rails_handsontable 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module RailsHandsontable
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Handsontable 0.10.1
2
+ * Handsontable 0.10.2
3
3
  * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs
4
4
  *
5
5
  * Copyright 2012, Marcin Warpechowski
6
6
  * Licensed under the MIT license.
7
7
  * http://handsontable.com/
8
8
  *
9
- * Date: Sun Jan 12 2014 13:55:41 GMT+0100 (Central European Standard Time)
9
+ * Date: Thu Jan 23 2014 23:06:23 GMT+0100 (CET)
10
10
  */
11
11
  /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */
12
12
 
@@ -227,486 +227,9 @@ Handsontable.Core = function (rootElement, userSettings) {
227
227
  isPopulated: null,
228
228
  scrollable: null,
229
229
  extensions: {},
230
- colToProp: null,
231
- propToCol: null,
232
- dataSchema: null,
233
- dataType: 'array',
234
230
  firstRun: true
235
231
  };
236
232
 
237
- datamap = {
238
- recursiveDuckSchema: function (obj) {
239
- var schema;
240
- if ($.isPlainObject(obj)) {
241
- schema = {};
242
- for (var i in obj) {
243
- if (obj.hasOwnProperty(i)) {
244
- if ($.isPlainObject(obj[i])) {
245
- schema[i] = datamap.recursiveDuckSchema(obj[i]);
246
- }
247
- else {
248
- schema[i] = null;
249
- }
250
- }
251
- }
252
- }
253
- else {
254
- schema = [];
255
- }
256
- return schema;
257
- },
258
-
259
- recursiveDuckColumns: function (schema, lastCol, parent) {
260
- var prop, i;
261
- if (typeof lastCol === 'undefined') {
262
- lastCol = 0;
263
- parent = '';
264
- }
265
- if ($.isPlainObject(schema)) {
266
- for (i in schema) {
267
- if (schema.hasOwnProperty(i)) {
268
- if (schema[i] === null) {
269
- prop = parent + i;
270
- priv.colToProp.push(prop);
271
- priv.propToCol[prop] = lastCol;
272
- lastCol++;
273
- }
274
- else {
275
- lastCol = datamap.recursiveDuckColumns(schema[i], lastCol, i + '.');
276
- }
277
- }
278
- }
279
- }
280
- return lastCol;
281
- },
282
-
283
- createMap: function () {
284
- if (typeof datamap.getSchema() === "undefined") {
285
- throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`");
286
- }
287
- var i, ilen, schema = datamap.getSchema();
288
- priv.colToProp = [];
289
- priv.propToCol = {};
290
- if (priv.settings.columns) {
291
- for (i = 0, ilen = priv.settings.columns.length; i < ilen; i++) {
292
- priv.colToProp[i] = priv.settings.columns[i].data;
293
- priv.propToCol[priv.settings.columns[i].data] = i;
294
- }
295
- }
296
- else {
297
- datamap.recursiveDuckColumns(schema);
298
- }
299
- },
300
-
301
- colToProp: function (col) {
302
- col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
303
- if (priv.colToProp && typeof priv.colToProp[col] !== 'undefined') {
304
- return priv.colToProp[col];
305
- }
306
- else {
307
- return col;
308
- }
309
- },
310
-
311
- propToCol: function (prop) {
312
- var col;
313
- if (typeof priv.propToCol[prop] !== 'undefined') {
314
- col = priv.propToCol[prop];
315
- }
316
- else {
317
- col = prop;
318
- }
319
- col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
320
- return col;
321
- },
322
-
323
- getSchema: function () {
324
- if (priv.settings.dataSchema) {
325
- if (typeof priv.settings.dataSchema === 'function') {
326
- return priv.settings.dataSchema();
327
- }
328
- return priv.settings.dataSchema;
329
- }
330
- return priv.duckDataSchema;
331
- },
332
-
333
- /**
334
- * Creates row at the bottom of the data array
335
- * @param {Number} [index] Optional. Index of the row before which the new row will be inserted
336
- */
337
- createRow: function (index, amount) {
338
- var row
339
- , colCount = instance.countCols()
340
- , numberOfCreatedRows = 0
341
- , currentIndex;
342
-
343
- if (!amount) {
344
- amount = 1;
345
- }
346
-
347
- if (typeof index !== 'number' || index >= instance.countRows()) {
348
- index = instance.countRows();
349
- }
350
-
351
- currentIndex = index;
352
- while (numberOfCreatedRows < amount && instance.countRows() < priv.settings.maxRows) {
353
-
354
- if (priv.dataType === 'array') {
355
- row = [];
356
- for (var c = 0; c < colCount; c++) {
357
- row.push(null);
358
- }
359
- }
360
- else if (priv.dataType === 'function') {
361
- row = priv.settings.dataSchema(index);
362
- }
363
- else {
364
- row = $.extend(true, {}, datamap.getSchema());
365
- }
366
-
367
- if (index === instance.countRows()) {
368
- GridSettings.prototype.data.push(row);
369
- }
370
- else {
371
- GridSettings.prototype.data.splice(index, 0, row);
372
- }
373
-
374
- numberOfCreatedRows++;
375
- currentIndex++;
376
- }
377
-
378
-
379
- instance.PluginHooks.run('afterCreateRow', index, numberOfCreatedRows);
380
- instance.forceFullRender = true; //used when data was changed
381
-
382
- return numberOfCreatedRows;
383
- },
384
-
385
- /**
386
- * Creates col at the right of the data array
387
- * @param {Number} [index] Optional. Index of the column before which the new column will be inserted
388
- * * @param {Number} [amount] Optional.
389
- */
390
- createCol: function (index, amount) {
391
- if (priv.dataType === 'object' || priv.settings.columns) {
392
- throw new Error("Cannot create new column. When data source in an object, " +
393
- "you can only have as much columns as defined in first data row, data schema or in the 'columns' setting." +
394
- "If you want to be able to add new columns, you have to use array datasource.");
395
- }
396
- var rlen = instance.countRows()
397
- , data = GridSettings.prototype.data
398
- , constructor
399
- , numberOfCreatedCols = 0
400
- , currentIndex;
401
-
402
- if (!amount) {
403
- amount = 1;
404
- }
405
-
406
- currentIndex = index;
407
-
408
- while (numberOfCreatedCols < amount && instance.countCols() < priv.settings.maxCols){
409
- constructor = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
410
- if (typeof index !== 'number' || index >= instance.countCols()) {
411
- for (var r = 0; r < rlen; r++) {
412
- if (typeof data[r] === 'undefined') {
413
- data[r] = [];
414
- }
415
- data[r].push(null);
416
- }
417
- // Add new column constructor
418
- priv.columnSettings.push(constructor);
419
- }
420
- else {
421
- for (var r = 0 ; r < rlen; r++) {
422
- data[r].splice(currentIndex, 0, null);
423
- }
424
- // Add new column constructor at given index
425
- priv.columnSettings.splice(currentIndex, 0, constructor);
426
- }
427
-
428
- numberOfCreatedCols++;
429
- currentIndex++;
430
- }
431
-
432
- instance.PluginHooks.run('afterCreateCol', index, numberOfCreatedCols);
433
- instance.forceFullRender = true; //used when data was changed
434
-
435
- return numberOfCreatedCols;
436
- },
437
-
438
- /**
439
- * Removes row from the data array
440
- * @param {Number} [index] Optional. Index of the row to be removed. If not provided, the last row will be removed
441
- * @param {Number} [amount] Optional. Amount of the rows to be removed. If not provided, one row will be removed
442
- */
443
- removeRow: function (index, amount) {
444
- if (!amount) {
445
- amount = 1;
446
- }
447
- if (typeof index !== 'number') {
448
- index = -amount;
449
- }
450
-
451
- index = (instance.countRows() + index) % instance.countRows();
452
-
453
- // We have to map the physical row ids to logical and than perform removing with (possibly) new row id
454
- var logicRows = this.physicalRowsToLogical(index, amount);
455
-
456
- var actionWasNotCancelled = instance.PluginHooks.execute('beforeRemoveRow', index, amount);
457
-
458
- if(actionWasNotCancelled === false){
459
- return;
460
- }
461
-
462
- var newData = GridSettings.prototype.data.filter(function (row, index) {
463
- return logicRows.indexOf(index) == -1;
464
- });
465
-
466
- GridSettings.prototype.data.length = 0;
467
- Array.prototype.push.apply(GridSettings.prototype.data, newData);
468
-
469
- instance.PluginHooks.run('afterRemoveRow', index, amount);
470
-
471
- instance.forceFullRender = true; //used when data was changed
472
- },
473
-
474
- /**
475
- * Removes column from the data array
476
- * @param {Number} [index] Optional. Index of the column to be removed. If not provided, the last column will be removed
477
- * @param {Number} [amount] Optional. Amount of the columns to be removed. If not provided, one column will be removed
478
- */
479
- removeCol: function (index, amount) {
480
- if (priv.dataType === 'object' || priv.settings.columns) {
481
- throw new Error("cannot remove column with object data source or columns option specified");
482
- }
483
- if (!amount) {
484
- amount = 1;
485
- }
486
- if (typeof index !== 'number') {
487
- index = -amount;
488
- }
489
-
490
- index = (instance.countCols() + index) % instance.countCols();
491
-
492
- var actionWasNotCancelled = instance.PluginHooks.execute('beforeRemoveCol', index, amount);
493
-
494
- if(actionWasNotCancelled === false){
495
- return;
496
- }
497
-
498
- var data = GridSettings.prototype.data;
499
- for (var r = 0, rlen = instance.countRows(); r < rlen; r++) {
500
- data[r].splice(index, amount);
501
- }
502
- priv.columnSettings.splice(index, amount);
503
-
504
- instance.PluginHooks.run('afterRemoveCol', index, amount);
505
- instance.forceFullRender = true; //used when data was changed
506
- },
507
-
508
- /**
509
- * Add / removes data from the column
510
- * @param {Number} col Index of column in which do you want to do splice.
511
- * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
512
- * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
513
- * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
514
- */
515
- spliceCol: function (col, index, amount/*, elements...*/) {
516
- var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
517
-
518
- var colData = instance.getDataAtCol(col);
519
- var removed = colData.slice(index, index + amount);
520
- var after = colData.slice(index + amount);
521
-
522
- Handsontable.helper.extendArray(elements, after);
523
- var i = 0;
524
- while (i < amount) {
525
- elements.push(null); //add null in place of removed elements
526
- i++;
527
- }
528
- Handsontable.helper.to2dArray(elements);
529
- instance.populateFromArray(index, col, elements, null, null, 'spliceCol');
530
-
531
- return removed;
532
- },
533
-
534
- /**
535
- * Add / removes data from the row
536
- * @param {Number} row Index of row in which do you want to do splice.
537
- * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
538
- * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
539
- * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
540
- */
541
- spliceRow: function (row, index, amount/*, elements...*/) {
542
- var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
543
-
544
- var rowData = instance.getDataAtRow(row);
545
- var removed = rowData.slice(index, index + amount);
546
- var after = rowData.slice(index + amount);
547
-
548
- Handsontable.helper.extendArray(elements, after);
549
- var i = 0;
550
- while (i < amount) {
551
- elements.push(null); //add null in place of removed elements
552
- i++;
553
- }
554
- instance.populateFromArray(row, index, [elements], null, null, 'spliceRow');
555
-
556
- return removed;
557
- },
558
-
559
- /**
560
- * Returns single value from the data array
561
- * @param {Number} row
562
- * @param {Number} prop
563
- */
564
- getVars: {},
565
- get: function (row, prop) {
566
- datamap.getVars.row = row;
567
- datamap.getVars.prop = prop;
568
- instance.PluginHooks.run('beforeGet', datamap.getVars);
569
- if (typeof datamap.getVars.prop === 'string' && datamap.getVars.prop.indexOf('.') > -1) {
570
- var sliced = datamap.getVars.prop.split(".");
571
- var out = priv.settings.data[datamap.getVars.row];
572
- if (!out) {
573
- return null;
574
- }
575
- for (var i = 0, ilen = sliced.length; i < ilen; i++) {
576
- out = out[sliced[i]];
577
- if (typeof out === 'undefined') {
578
- return null;
579
- }
580
- }
581
- return out;
582
- }
583
- else if (typeof datamap.getVars.prop === 'function') {
584
- /**
585
- * allows for interacting with complex structures, for example
586
- * d3/jQuery getter/setter properties:
587
- *
588
- * {columns: [{
589
- * data: function(row, value){
590
- * if(arguments.length === 1){
591
- * return row.property();
592
- * }
593
- * row.property(value);
594
- * }
595
- * }]}
596
- */
597
- return datamap.getVars.prop(priv.settings.data.slice(
598
- datamap.getVars.row,
599
- datamap.getVars.row + 1
600
- )[0]);
601
- }
602
- else {
603
- return priv.settings.data[datamap.getVars.row] ? priv.settings.data[datamap.getVars.row][datamap.getVars.prop] : null;
604
- }
605
- },
606
-
607
- /**
608
- * Saves single value to the data array
609
- * @param {Number} row
610
- * @param {Number} prop
611
- * @param {String} value
612
- * @param {String} [source] Optional. Source of hook runner.
613
- */
614
- setVars: {},
615
- set: function (row, prop, value, source) {
616
- datamap.setVars.row = row;
617
- datamap.setVars.prop = prop;
618
- datamap.setVars.value = value;
619
- instance.PluginHooks.run('beforeSet', datamap.setVars, source || "datamapGet");
620
- if (typeof datamap.setVars.prop === 'string' && datamap.setVars.prop.indexOf('.') > -1) {
621
- var sliced = datamap.setVars.prop.split(".");
622
- var out = priv.settings.data[datamap.setVars.row];
623
- for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) {
624
- out = out[sliced[i]];
625
- }
626
- out[sliced[i]] = datamap.setVars.value;
627
- }
628
- else if (typeof datamap.setVars.prop === 'function') {
629
- /* see the `function` handler in `get` */
630
- datamap.setVars.prop(priv.settings.data.slice(
631
- datamap.setVars.row,
632
- datamap.setVars.row + 1
633
- )[0], datamap.setVars.value);
634
- }
635
- else {
636
- priv.settings.data[datamap.setVars.row][datamap.setVars.prop] = datamap.setVars.value;
637
- }
638
- },
639
- /**
640
- * This ridiculous piece of code maps rows Id that are present in table data to those displayed for user.
641
- * The trick is, the physical row id (stored in settings.data) is not necessary the same
642
- * as the logical (displayed) row id (e.g. when sorting is applied).
643
- */
644
- physicalRowsToLogical: function (index, amount) {
645
- var physicRow = (GridSettings.prototype.data.length + index) % GridSettings.prototype.data.length;
646
- var logicRows = [];
647
- var rowsToRemove = amount;
648
-
649
- while (physicRow < GridSettings.prototype.data.length && rowsToRemove) {
650
- this.get(physicRow, 0); //this performs an actual mapping and saves the result to getVars
651
- logicRows.push(this.getVars.row);
652
-
653
- rowsToRemove--;
654
- physicRow++;
655
- }
656
-
657
- return logicRows;
658
- },
659
-
660
- /**
661
- * Clears the data array
662
- */
663
- clear: function () {
664
- for (var r = 0; r < instance.countRows(); r++) {
665
- for (var c = 0; c < instance.countCols(); c++) {
666
- datamap.set(r, datamap.colToProp(c), '');
667
- }
668
- }
669
- },
670
-
671
- /**
672
- * Returns the data array
673
- * @return {Array}
674
- */
675
- getAll: function () {
676
- return priv.settings.data;
677
- },
678
-
679
- /**
680
- * Returns data range as array
681
- * @param {Object} start Start selection position
682
- * @param {Object} end End selection position
683
- * @return {Array}
684
- */
685
- getRange: function (start, end) {
686
- var r, rlen, c, clen, output = [], row;
687
- rlen = Math.max(start.row, end.row);
688
- clen = Math.max(start.col, end.col);
689
- for (r = Math.min(start.row, end.row); r <= rlen; r++) {
690
- row = [];
691
- for (c = Math.min(start.col, end.col); c <= clen; c++) {
692
- row.push(datamap.get(r, datamap.colToProp(c)));
693
- }
694
- output.push(row);
695
- }
696
- return output;
697
- },
698
-
699
- /**
700
- * Return data as text (tab separated columns)
701
- * @param {Object} start (Optional) Start selection position
702
- * @param {Object} end (Optional) End selection position
703
- * @return {String}
704
- */
705
- getText: function (start, end) {
706
- return SheetClip.stringify(datamap.getRange(start, end));
707
- }
708
- };
709
-
710
233
  grid = {
711
234
  /**
712
235
  * Inserts or removes rows and columns
@@ -828,7 +351,7 @@ Handsontable.Core = function (rootElement, userSettings) {
828
351
  }
829
352
 
830
353
  //should I add empty cols to meet minSpareCols?
831
- if (!priv.settings.columns && priv.dataType === 'array' && emptyCols < priv.settings.minSpareCols) {
354
+ if (!priv.settings.columns && instance.dataType === 'array' && emptyCols < priv.settings.minSpareCols) {
832
355
  for (; emptyCols < priv.settings.minSpareCols && instance.countCols() < priv.settings.maxCols; emptyCols++) {
833
356
  datamap.createCol();
834
357
  }
@@ -1580,7 +1103,7 @@ Handsontable.Core = function (rootElement, userSettings) {
1580
1103
  }
1581
1104
  }
1582
1105
 
1583
- if (priv.dataType === 'array' && priv.settings.minSpareCols) {
1106
+ if (instance.dataType === 'array' && priv.settings.minSpareCols) {
1584
1107
  while (datamap.propToCol(changes[i][1]) > instance.countCols() - 1) {
1585
1108
  datamap.createCol();
1586
1109
  }
@@ -1779,7 +1302,7 @@ Handsontable.Core = function (rootElement, userSettings) {
1779
1302
  * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
1780
1303
  */
1781
1304
  this.spliceCol = function (col, index, amount/*, elements... */) {
1782
- return datamap.spliceCol.apply(null, arguments);
1305
+ return datamap.spliceCol.apply(datamap, arguments);
1783
1306
  };
1784
1307
 
1785
1308
  /**
@@ -1790,7 +1313,7 @@ Handsontable.Core = function (rootElement, userSettings) {
1790
1313
  * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
1791
1314
  */
1792
1315
  this.spliceRow = function (row, index, amount/*, elements... */) {
1793
- return datamap.spliceRow.apply(null, arguments);
1316
+ return datamap.spliceRow.apply(datamap, arguments);
1794
1317
  };
1795
1318
 
1796
1319
  /**
@@ -1902,22 +1425,17 @@ Handsontable.Core = function (rootElement, userSettings) {
1902
1425
  GridSettings.prototype.data = data;
1903
1426
 
1904
1427
  if (priv.settings.dataSchema instanceof Array || data[0] instanceof Array) {
1905
- priv.dataType = 'array';
1428
+ instance.dataType = 'array';
1906
1429
  }
1907
1430
  else if (typeof priv.settings.dataSchema === 'function') {
1908
- priv.dataType = 'function';
1431
+ instance.dataType = 'function';
1909
1432
  }
1910
1433
  else {
1911
- priv.dataType = 'object';
1434
+ instance.dataType = 'object';
1912
1435
  }
1913
1436
 
1914
- if (data[0]) {
1915
- priv.duckDataSchema = datamap.recursiveDuckSchema(data[0]);
1916
- }
1917
- else {
1918
- priv.duckDataSchema = {};
1919
- }
1920
- datamap.createMap();
1437
+ datamap = new Handsontable.DataMap(instance, priv, GridSettings);
1438
+
1921
1439
  clearCellSettingCache();
1922
1440
 
1923
1441
  grid.adjustRowsAndCols();
@@ -1954,12 +1472,12 @@ Handsontable.Core = function (rootElement, userSettings) {
1954
1472
  return datamap.getAll();
1955
1473
  }
1956
1474
  else {
1957
- return datamap.getRange({row: r, col: c}, {row: r2, col: c2});
1475
+ return datamap.getRange({row: r, col: c}, {row: r2, col: c2}, datamap.DESTINATION_RENDERER);
1958
1476
  }
1959
1477
  };
1960
1478
 
1961
1479
  this.getCopyableData = function (startRow, startCol, endRow, endCol) {
1962
- return datamap.getText({row: startRow, col: startCol}, {row: endRow, col: endCol});
1480
+ return datamap.getCopyableText({row: startRow, col: startCol}, {row: endRow, col: endCol});
1963
1481
  }
1964
1482
 
1965
1483
  /**
@@ -2204,7 +1722,7 @@ Handsontable.Core = function (rootElement, userSettings) {
2204
1722
  * @return value (mixed data type)
2205
1723
  */
2206
1724
  this.getDataAtCol = function (col) {
2207
- return [].concat.apply([], datamap.getRange({row: 0, col: col}, {row: priv.settings.data.length - 1, col: col}));
1725
+ return [].concat.apply([], datamap.getRange({row: 0, col: col}, {row: priv.settings.data.length - 1, col: col}, datamap.DESTINATION_RENDERER));
2208
1726
  };
2209
1727
 
2210
1728
  /**
@@ -2214,7 +1732,7 @@ Handsontable.Core = function (rootElement, userSettings) {
2214
1732
  * @return value (mixed data type)
2215
1733
  */
2216
1734
  this.getDataAtProp = function (prop) {
2217
- return [].concat.apply([], datamap.getRange({row: 0, col: datamap.propToCol(prop)}, {row: priv.settings.data.length - 1, col: datamap.propToCol(prop)}));
1735
+ return [].concat.apply([], datamap.getRange({row: 0, col: datamap.propToCol(prop)}, {row: priv.settings.data.length - 1, col: datamap.propToCol(prop)}, datamap.DESTINATION_RENDERER));
2218
1736
  };
2219
1737
 
2220
1738
  /**
@@ -2300,8 +1818,9 @@ Handsontable.Core = function (rootElement, userSettings) {
2300
1818
  }
2301
1819
  };
2302
1820
 
1821
+ var rendererLookup = Handsontable.helper.cellMethodLookupFactory('renderer');
2303
1822
  this.getCellRenderer = function (row, col) {
2304
- var renderer = Handsontable.helper.cellMethodLookupFactory('renderer').call(this, row, col);
1823
+ var renderer = rendererLookup.call(this, row, col);
2305
1824
  return Handsontable.renderers.getRenderer(renderer);
2306
1825
 
2307
1826
  };
@@ -2477,15 +1996,15 @@ Handsontable.Core = function (rootElement, userSettings) {
2477
1996
  * @return {Number}
2478
1997
  */
2479
1998
  this.countCols = function () {
2480
- if (priv.dataType === 'object' || priv.dataType === 'function') {
1999
+ if (instance.dataType === 'object' || instance.dataType === 'function') {
2481
2000
  if (priv.settings.columns && priv.settings.columns.length) {
2482
2001
  return priv.settings.columns.length;
2483
2002
  }
2484
2003
  else {
2485
- return priv.colToProp.length;
2004
+ return datamap.colToPropCache.length;
2486
2005
  }
2487
2006
  }
2488
- else if (priv.dataType === 'array') {
2007
+ else if (instance.dataType === 'array') {
2489
2008
  if (priv.settings.columns && priv.settings.columns.length) {
2490
2009
  return priv.settings.columns.length;
2491
2010
  }
@@ -2752,7 +2271,7 @@ Handsontable.Core = function (rootElement, userSettings) {
2752
2271
  /**
2753
2272
  * Handsontable version
2754
2273
  */
2755
- this.version = '0.10.1'; //inserted by grunt from package.json
2274
+ this.version = '0.10.2'; //inserted by grunt from package.json
2756
2275
  };
2757
2276
 
2758
2277
  var DefaultSettings = function () {};
@@ -2816,6 +2335,7 @@ DefaultSettings.prototype = {
2816
2335
  readOnly: false,
2817
2336
  nativeScrollbars: false,
2818
2337
  type: 'text',
2338
+ copyable: true,
2819
2339
  debug: false //shows debug overlays in Walkontable
2820
2340
  };
2821
2341
  Handsontable.DefaultSettings = DefaultSettings;
@@ -4200,6 +3720,545 @@ Handsontable.SelectionPoint.prototype.arr = function (arr) {
4200
3720
  }
4201
3721
  return [this._row, this._col]
4202
3722
  };
3723
+ (function (Handsontable) {
3724
+ 'use strict';
3725
+
3726
+ /**
3727
+ * Utility class that gets and saves data from/to the data source using mapping of columns numbers to object property names
3728
+ * TODO refactor arguments of methods getRange, getText to be numbers (not objects)
3729
+ * TODO remove priv, GridSettings from object constructor
3730
+ *
3731
+ * @param instance
3732
+ * @param priv
3733
+ * @param GridSettings
3734
+ * @constructor
3735
+ */
3736
+ Handsontable.DataMap = function (instance, priv, GridSettings) {
3737
+ this.instance = instance;
3738
+ this.priv = priv;
3739
+ this.GridSettings = GridSettings;
3740
+ this.dataSource = this.instance.getSettings().data;
3741
+
3742
+ if (this.dataSource[0]) {
3743
+ this.duckSchema = this.recursiveDuckSchema(this.dataSource[0]);
3744
+ }
3745
+ else {
3746
+ this.duckSchema = {};
3747
+ }
3748
+ this.createMap();
3749
+
3750
+ this.getVars = {}; //used by modifier
3751
+ this.setVars = {}; //used by modifier
3752
+ };
3753
+
3754
+ Handsontable.DataMap.prototype.DESTINATION_RENDERER = 1;
3755
+ Handsontable.DataMap.prototype.DESTINATION_CLIPBOARD_GENERATOR = 2;
3756
+
3757
+ Handsontable.DataMap.prototype.recursiveDuckSchema = function (obj) {
3758
+ var schema;
3759
+ if ($.isPlainObject(obj)) {
3760
+ schema = {};
3761
+ for (var i in obj) {
3762
+ if (obj.hasOwnProperty(i)) {
3763
+ if ($.isPlainObject(obj[i])) {
3764
+ schema[i] = this.recursiveDuckSchema(obj[i]);
3765
+ }
3766
+ else {
3767
+ schema[i] = null;
3768
+ }
3769
+ }
3770
+ }
3771
+ }
3772
+ else {
3773
+ schema = [];
3774
+ }
3775
+ return schema;
3776
+ };
3777
+
3778
+ Handsontable.DataMap.prototype.recursiveDuckColumns = function (schema, lastCol, parent) {
3779
+ var prop, i;
3780
+ if (typeof lastCol === 'undefined') {
3781
+ lastCol = 0;
3782
+ parent = '';
3783
+ }
3784
+ if ($.isPlainObject(schema)) {
3785
+ for (i in schema) {
3786
+ if (schema.hasOwnProperty(i)) {
3787
+ if (schema[i] === null) {
3788
+ prop = parent + i;
3789
+ this.colToPropCache.push(prop);
3790
+ this.propToColCache[prop] = lastCol;
3791
+ lastCol++;
3792
+ }
3793
+ else {
3794
+ lastCol = this.recursiveDuckColumns(schema[i], lastCol, i + '.');
3795
+ }
3796
+ }
3797
+ }
3798
+ }
3799
+ return lastCol;
3800
+ };
3801
+
3802
+ Handsontable.DataMap.prototype.createMap = function () {
3803
+ if (typeof this.getSchema() === "undefined") {
3804
+ throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`");
3805
+ }
3806
+ var i, ilen, schema = this.getSchema();
3807
+ this.colToPropCache = [];
3808
+ this.propToColCache = {};
3809
+ var columns = this.instance.getSettings().columns;
3810
+ if (columns) {
3811
+ for (i = 0, ilen = columns.length; i < ilen; i++) {
3812
+ this.colToPropCache[i] = columns[i].data;
3813
+ this.propToColCache[columns[i].data] = i;
3814
+ }
3815
+ }
3816
+ else {
3817
+ this.recursiveDuckColumns(schema);
3818
+ }
3819
+ };
3820
+
3821
+ Handsontable.DataMap.prototype.colToProp = function (col) {
3822
+ col = Handsontable.PluginHooks.execute(this.instance, 'modifyCol', col);
3823
+ if (this.colToPropCache && typeof this.colToPropCache[col] !== 'undefined') {
3824
+ return this.colToPropCache[col];
3825
+ }
3826
+ else {
3827
+ return col;
3828
+ }
3829
+ };
3830
+
3831
+ Handsontable.DataMap.prototype.propToCol = function (prop) {
3832
+ var col;
3833
+ if (typeof this.propToColCache[prop] !== 'undefined') {
3834
+ col = this.propToColCache[prop];
3835
+ }
3836
+ else {
3837
+ col = prop;
3838
+ }
3839
+ col = Handsontable.PluginHooks.execute(this.instance, 'modifyCol', col);
3840
+ return col;
3841
+ };
3842
+
3843
+ Handsontable.DataMap.prototype.getSchema = function () {
3844
+ var schema = this.instance.getSettings().dataSchema;
3845
+ if (schema) {
3846
+ if (typeof schema === 'function') {
3847
+ return schema();
3848
+ }
3849
+ return schema;
3850
+ }
3851
+ return this.duckSchema;
3852
+ };
3853
+
3854
+ /**
3855
+ * Creates row at the bottom of the data array
3856
+ * @param {Number} [index] Optional. Index of the row before which the new row will be inserted
3857
+ */
3858
+ Handsontable.DataMap.prototype.createRow = function (index, amount) {
3859
+ var row
3860
+ , colCount = this.instance.countCols()
3861
+ , numberOfCreatedRows = 0
3862
+ , currentIndex;
3863
+
3864
+ if (!amount) {
3865
+ amount = 1;
3866
+ }
3867
+
3868
+ if (typeof index !== 'number' || index >= this.instance.countRows()) {
3869
+ index = this.instance.countRows();
3870
+ }
3871
+
3872
+ currentIndex = index;
3873
+ var maxRows = this.instance.getSettings().maxRows;
3874
+ while (numberOfCreatedRows < amount && this.instance.countRows() < maxRows) {
3875
+
3876
+ if (this.instance.dataType === 'array') {
3877
+ row = [];
3878
+ for (var c = 0; c < colCount; c++) {
3879
+ row.push(null);
3880
+ }
3881
+ }
3882
+ else if (this.instance.dataType === 'function') {
3883
+ row = this.instance.getSettings().dataSchema(index);
3884
+ }
3885
+ else {
3886
+ row = $.extend(true, {}, this.getSchema());
3887
+ }
3888
+
3889
+ if (index === this.instance.countRows()) {
3890
+ this.dataSource.push(row);
3891
+ }
3892
+ else {
3893
+ this.dataSource.splice(index, 0, row);
3894
+ }
3895
+
3896
+ numberOfCreatedRows++;
3897
+ currentIndex++;
3898
+ }
3899
+
3900
+
3901
+ this.instance.PluginHooks.run('afterCreateRow', index, numberOfCreatedRows);
3902
+ this.instance.forceFullRender = true; //used when data was changed
3903
+
3904
+ return numberOfCreatedRows;
3905
+ };
3906
+
3907
+ /**
3908
+ * Creates col at the right of the data array
3909
+ * @param {Number} [index] Optional. Index of the column before which the new column will be inserted
3910
+ * * @param {Number} [amount] Optional.
3911
+ */
3912
+ Handsontable.DataMap.prototype.createCol = function (index, amount) {
3913
+ if (this.instance.dataType === 'object' || this.instance.getSettings().columns) {
3914
+ throw new Error("Cannot create new column. When data source in an object, " +
3915
+ "you can only have as much columns as defined in first data row, data schema or in the 'columns' setting." +
3916
+ "If you want to be able to add new columns, you have to use array datasource.");
3917
+ }
3918
+ var rlen = this.instance.countRows()
3919
+ , data = this.dataSource
3920
+ , constructor
3921
+ , numberOfCreatedCols = 0
3922
+ , currentIndex;
3923
+
3924
+ if (!amount) {
3925
+ amount = 1;
3926
+ }
3927
+
3928
+ currentIndex = index;
3929
+
3930
+ var maxCols = this.instance.getSettings().maxCols;
3931
+ while (numberOfCreatedCols < amount && this.instance.countCols() < maxCols) {
3932
+ constructor = Handsontable.helper.columnFactory(this.GridSettings, this.priv.columnsSettingConflicts);
3933
+ if (typeof index !== 'number' || index >= this.instance.countCols()) {
3934
+ for (var r = 0; r < rlen; r++) {
3935
+ if (typeof data[r] === 'undefined') {
3936
+ data[r] = [];
3937
+ }
3938
+ data[r].push(null);
3939
+ }
3940
+ // Add new column constructor
3941
+ this.priv.columnSettings.push(constructor);
3942
+ }
3943
+ else {
3944
+ for (var r = 0; r < rlen; r++) {
3945
+ data[r].splice(currentIndex, 0, null);
3946
+ }
3947
+ // Add new column constructor at given index
3948
+ this.priv.columnSettings.splice(currentIndex, 0, constructor);
3949
+ }
3950
+
3951
+ numberOfCreatedCols++;
3952
+ currentIndex++;
3953
+ }
3954
+
3955
+ this.instance.PluginHooks.run('afterCreateCol', index, numberOfCreatedCols);
3956
+ this.instance.forceFullRender = true; //used when data was changed
3957
+
3958
+ return numberOfCreatedCols;
3959
+ };
3960
+
3961
+ /**
3962
+ * Removes row from the data array
3963
+ * @param {Number} [index] Optional. Index of the row to be removed. If not provided, the last row will be removed
3964
+ * @param {Number} [amount] Optional. Amount of the rows to be removed. If not provided, one row will be removed
3965
+ */
3966
+ Handsontable.DataMap.prototype.removeRow = function (index, amount) {
3967
+ if (!amount) {
3968
+ amount = 1;
3969
+ }
3970
+ if (typeof index !== 'number') {
3971
+ index = -amount;
3972
+ }
3973
+
3974
+ index = (this.instance.countRows() + index) % this.instance.countRows();
3975
+
3976
+ // We have to map the physical row ids to logical and than perform removing with (possibly) new row id
3977
+ var logicRows = this.physicalRowsToLogical(index, amount);
3978
+
3979
+ var actionWasNotCancelled = this.instance.PluginHooks.execute('beforeRemoveRow', index, amount);
3980
+
3981
+ if (actionWasNotCancelled === false) {
3982
+ return;
3983
+ }
3984
+
3985
+ var data = this.dataSource;
3986
+ var newData = data.filter(function (row, index) {
3987
+ return logicRows.indexOf(index) == -1;
3988
+ });
3989
+
3990
+ data.length = 0;
3991
+ Array.prototype.push.apply(data, newData);
3992
+
3993
+ this.instance.PluginHooks.run('afterRemoveRow', index, amount);
3994
+
3995
+ this.instance.forceFullRender = true; //used when data was changed
3996
+ };
3997
+
3998
+ /**
3999
+ * Removes column from the data array
4000
+ * @param {Number} [index] Optional. Index of the column to be removed. If not provided, the last column will be removed
4001
+ * @param {Number} [amount] Optional. Amount of the columns to be removed. If not provided, one column will be removed
4002
+ */
4003
+ Handsontable.DataMap.prototype.removeCol = function (index, amount) {
4004
+ if (this.instance.dataType === 'object' || this.instance.getSettings().columns) {
4005
+ throw new Error("cannot remove column with object data source or columns option specified");
4006
+ }
4007
+ if (!amount) {
4008
+ amount = 1;
4009
+ }
4010
+ if (typeof index !== 'number') {
4011
+ index = -amount;
4012
+ }
4013
+
4014
+ index = (this.instance.countCols() + index) % this.instance.countCols();
4015
+
4016
+ var actionWasNotCancelled = this.instance.PluginHooks.execute('beforeRemoveCol', index, amount);
4017
+
4018
+ if (actionWasNotCancelled === false) {
4019
+ return;
4020
+ }
4021
+
4022
+ var data = this.dataSource;
4023
+ for (var r = 0, rlen = this.instance.countRows(); r < rlen; r++) {
4024
+ data[r].splice(index, amount);
4025
+ }
4026
+ this.priv.columnSettings.splice(index, amount);
4027
+
4028
+ this.instance.PluginHooks.run('afterRemoveCol', index, amount);
4029
+ this.instance.forceFullRender = true; //used when data was changed
4030
+ };
4031
+
4032
+ /**
4033
+ * Add / removes data from the column
4034
+ * @param {Number} col Index of column in which do you want to do splice.
4035
+ * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
4036
+ * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
4037
+ * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
4038
+ */
4039
+ Handsontable.DataMap.prototype.spliceCol = function (col, index, amount/*, elements...*/) {
4040
+ var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
4041
+
4042
+ var colData = this.instance.getDataAtCol(col);
4043
+ var removed = colData.slice(index, index + amount);
4044
+ var after = colData.slice(index + amount);
4045
+
4046
+ Handsontable.helper.extendArray(elements, after);
4047
+ var i = 0;
4048
+ while (i < amount) {
4049
+ elements.push(null); //add null in place of removed elements
4050
+ i++;
4051
+ }
4052
+ Handsontable.helper.to2dArray(elements);
4053
+ this.instance.populateFromArray(index, col, elements, null, null, 'spliceCol');
4054
+
4055
+ return removed;
4056
+ };
4057
+
4058
+ /**
4059
+ * Add / removes data from the row
4060
+ * @param {Number} row Index of row in which do you want to do splice.
4061
+ * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
4062
+ * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
4063
+ * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
4064
+ */
4065
+ Handsontable.DataMap.prototype.spliceRow = function (row, index, amount/*, elements...*/) {
4066
+ var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
4067
+
4068
+ var rowData = this.instance.getDataAtRow(row);
4069
+ var removed = rowData.slice(index, index + amount);
4070
+ var after = rowData.slice(index + amount);
4071
+
4072
+ Handsontable.helper.extendArray(elements, after);
4073
+ var i = 0;
4074
+ while (i < amount) {
4075
+ elements.push(null); //add null in place of removed elements
4076
+ i++;
4077
+ }
4078
+ this.instance.populateFromArray(row, index, [elements], null, null, 'spliceRow');
4079
+
4080
+ return removed;
4081
+ };
4082
+
4083
+ /**
4084
+ * Returns single value from the data array
4085
+ * @param {Number} row
4086
+ * @param {Number} prop
4087
+ */
4088
+ Handsontable.DataMap.prototype.get = function (row, prop) {
4089
+ this.getVars.row = row;
4090
+ this.getVars.prop = prop;
4091
+ this.instance.PluginHooks.run('beforeGet', this.getVars);
4092
+ if (typeof this.getVars.prop === 'string' && this.getVars.prop.indexOf('.') > -1) {
4093
+ var sliced = this.getVars.prop.split(".");
4094
+ var out = this.dataSource[this.getVars.row];
4095
+ if (!out) {
4096
+ return null;
4097
+ }
4098
+ for (var i = 0, ilen = sliced.length; i < ilen; i++) {
4099
+ out = out[sliced[i]];
4100
+ if (typeof out === 'undefined') {
4101
+ return null;
4102
+ }
4103
+ }
4104
+ return out;
4105
+ }
4106
+ else if (typeof this.getVars.prop === 'function') {
4107
+ /**
4108
+ * allows for interacting with complex structures, for example
4109
+ * d3/jQuery getter/setter properties:
4110
+ *
4111
+ * {columns: [{
4112
+ * data: function(row, value){
4113
+ * if(arguments.length === 1){
4114
+ * return row.property();
4115
+ * }
4116
+ * row.property(value);
4117
+ * }
4118
+ * }]}
4119
+ */
4120
+ return this.getVars.prop(this.dataSource.slice(
4121
+ this.getVars.row,
4122
+ this.getVars.row + 1
4123
+ )[0]);
4124
+ }
4125
+ else {
4126
+ return this.dataSource[this.getVars.row] ? this.dataSource[this.getVars.row][this.getVars.prop] : null;
4127
+ }
4128
+ };
4129
+
4130
+ var copyableLookup = Handsontable.helper.cellMethodLookupFactory('copyable');
4131
+
4132
+ /**
4133
+ * Returns single value from the data array (intended for clipboard copy to an external application)
4134
+ * @param {Number} row
4135
+ * @param {Number} prop
4136
+ * @return {String}
4137
+ */
4138
+ Handsontable.DataMap.prototype.getCopyable = function (row, prop) {
4139
+ if (copyableLookup.call(this.instance, row, this.propToCol(prop))) {
4140
+ return this.get(row, prop);
4141
+ }
4142
+ return '';
4143
+ };
4144
+
4145
+ /**
4146
+ * Saves single value to the data array
4147
+ * @param {Number} row
4148
+ * @param {Number} prop
4149
+ * @param {String} value
4150
+ * @param {String} [source] Optional. Source of hook runner.
4151
+ */
4152
+ Handsontable.DataMap.prototype.set = function (row, prop, value, source) {
4153
+ this.setVars.row = row;
4154
+ this.setVars.prop = prop;
4155
+ this.setVars.value = value;
4156
+ this.instance.PluginHooks.run('beforeSet', this.setVars, source || "datamapGet");
4157
+ if (typeof this.setVars.prop === 'string' && this.setVars.prop.indexOf('.') > -1) {
4158
+ var sliced = this.setVars.prop.split(".");
4159
+ var out = this.dataSource[this.setVars.row];
4160
+ for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) {
4161
+ out = out[sliced[i]];
4162
+ }
4163
+ out[sliced[i]] = this.setVars.value;
4164
+ }
4165
+ else if (typeof this.setVars.prop === 'function') {
4166
+ /* see the `function` handler in `get` */
4167
+ this.setVars.prop(this.dataSource.slice(
4168
+ this.setVars.row,
4169
+ this.setVars.row + 1
4170
+ )[0], this.setVars.value);
4171
+ }
4172
+ else {
4173
+ this.dataSource[this.setVars.row][this.setVars.prop] = this.setVars.value;
4174
+ }
4175
+ };
4176
+
4177
+ /**
4178
+ * This ridiculous piece of code maps rows Id that are present in table data to those displayed for user.
4179
+ * The trick is, the physical row id (stored in settings.data) is not necessary the same
4180
+ * as the logical (displayed) row id (e.g. when sorting is applied).
4181
+ */
4182
+ Handsontable.DataMap.prototype.physicalRowsToLogical = function (index, amount) {
4183
+ var totalRows = this.instance.countRows();
4184
+ var physicRow = (totalRows + index) % totalRows;
4185
+ var logicRows = [];
4186
+ var rowsToRemove = amount;
4187
+
4188
+ while (physicRow < totalRows && rowsToRemove) {
4189
+ this.get(physicRow, 0); //this performs an actual mapping and saves the result to getVars
4190
+ logicRows.push(this.getVars.row);
4191
+
4192
+ rowsToRemove--;
4193
+ physicRow++;
4194
+ }
4195
+
4196
+ return logicRows;
4197
+ };
4198
+
4199
+ /**
4200
+ * Clears the data array
4201
+ */
4202
+ Handsontable.DataMap.prototype.clear = function () {
4203
+ for (var r = 0; r < this.instance.countRows(); r++) {
4204
+ for (var c = 0; c < this.instance.countCols(); c++) {
4205
+ this.set(r, this.colToProp(c), '');
4206
+ }
4207
+ }
4208
+ };
4209
+
4210
+ /**
4211
+ * Returns the data array
4212
+ * @return {Array}
4213
+ */
4214
+ Handsontable.DataMap.prototype.getAll = function () {
4215
+ return this.dataSource;
4216
+ };
4217
+
4218
+ /**
4219
+ * Returns data range as array
4220
+ * @param {Object} start Start selection position
4221
+ * @param {Object} end End selection position
4222
+ * @param {Number} destination Destination of datamap.get
4223
+ * @return {Array}
4224
+ */
4225
+ Handsontable.DataMap.prototype.getRange = function (start, end, destination) {
4226
+ var r, rlen, c, clen, output = [], row;
4227
+ var getFn = destination === this.DESTINATION_CLIPBOARD_GENERATOR ? this.getCopyable : this.get;
4228
+ rlen = Math.max(start.row, end.row);
4229
+ clen = Math.max(start.col, end.col);
4230
+ for (r = Math.min(start.row, end.row); r <= rlen; r++) {
4231
+ row = [];
4232
+ for (c = Math.min(start.col, end.col); c <= clen; c++) {
4233
+ row.push(getFn.call(this, r, this.colToProp(c)));
4234
+ }
4235
+ output.push(row);
4236
+ }
4237
+ return output;
4238
+ };
4239
+
4240
+ /**
4241
+ * Return data as text (tab separated columns)
4242
+ * @param {Object} start (Optional) Start selection position
4243
+ * @param {Object} end (Optional) End selection position
4244
+ * @return {String}
4245
+ */
4246
+ Handsontable.DataMap.prototype.getText = function (start, end) {
4247
+ return SheetClip.stringify(this.getRange(start, end, this.DESTINATION_RENDERER));
4248
+ };
4249
+
4250
+ /**
4251
+ * Return data as copyable text (tab separated columns intended for clipboard copy to an external application)
4252
+ * @param {Object} start (Optional) Start selection position
4253
+ * @param {Object} end (Optional) End selection position
4254
+ * @return {String}
4255
+ */
4256
+ Handsontable.DataMap.prototype.getCopyableText = function (start, end) {
4257
+ return SheetClip.stringify(this.getRange(start, end, this.DESTINATION_CLIPBOARD_GENERATOR));
4258
+ };
4259
+
4260
+ })(Handsontable);
4261
+
4203
4262
  (function (Handsontable) {
4204
4263
  'use strict';
4205
4264
 
@@ -5711,7 +5770,8 @@ Handsontable.HandsontableCell = {
5711
5770
 
5712
5771
  Handsontable.PasswordCell = {
5713
5772
  editor: Handsontable.editors.PasswordEditor,
5714
- renderer: Handsontable.renderers.PasswordRenderer
5773
+ renderer: Handsontable.renderers.PasswordRenderer,
5774
+ copyable: false
5715
5775
  };
5716
5776
 
5717
5777
  Handsontable.DropdownCell = {