PageRenderTime 32ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/files/jquery.handsontable/0.9.6/jquery.handsontable.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1482 lines | 1106 code | 156 blank | 220 comment | 297 complexity | 658e0916b71fae4df77c4f29f0be8648 MD5 | raw file
  1. /**
  2. * Handsontable 0.9.6
  3. * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs
  4. *
  5. * Copyright 2012, Marcin Warpechowski
  6. * Licensed under the MIT license.
  7. * http://handsontable.com/
  8. *
  9. * Date: Tue Jun 18 2013 18:56:33 GMT+0200 (Central European Daylight Time)
  10. */
  11. /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */
  12. var Handsontable = { //class namespace
  13. extension: {}, //extenstion namespace
  14. helper: {} //helper namespace
  15. };
  16. (function ($, window, Handsontable) {
  17. "use strict";
  18. /**
  19. * Handsontable constructor
  20. * @param rootElement The jQuery element in which Handsontable DOM will be inserted
  21. * @param userSettings
  22. * @constructor
  23. */
  24. Handsontable.Core = function (rootElement, userSettings) {
  25. var priv
  26. , datamap
  27. , grid
  28. , selection
  29. , editproxy
  30. , autofill
  31. , instance = this
  32. , GridSettings = function () {
  33. };
  34. Handsontable.helper.inherit(GridSettings, DefaultSettings); //create grid settings as a copy of default settings
  35. Handsontable.helper.extend(GridSettings.prototype, userSettings); //overwrite defaults with user settings
  36. this.rootElement = rootElement;
  37. this.guid = 'ht_' + Handsontable.helper.randomString(); //this is the namespace for global events
  38. if (!this.rootElement[0].id) {
  39. this.rootElement[0].id = this.guid; //if root element does not have an id, assign a random id
  40. }
  41. priv = {
  42. cellSettings: [],
  43. columnSettings: [],
  44. columnsSettingConflicts: ['data', 'width'],
  45. settings: new GridSettings(), // current settings instance
  46. settingsFromDOM: {},
  47. selStart: new Handsontable.SelectionPoint(),
  48. selEnd: new Handsontable.SelectionPoint(),
  49. editProxy: false,
  50. isPopulated: null,
  51. scrollable: null,
  52. undoRedo: null,
  53. extensions: {},
  54. colToProp: null,
  55. propToCol: null,
  56. dataSchema: null,
  57. dataType: 'array',
  58. firstRun: true
  59. };
  60. datamap = {
  61. recursiveDuckSchema: function (obj) {
  62. var schema;
  63. if ($.isPlainObject(obj)) {
  64. schema = {};
  65. for (var i in obj) {
  66. if (obj.hasOwnProperty(i)) {
  67. if ($.isPlainObject(obj[i])) {
  68. schema[i] = datamap.recursiveDuckSchema(obj[i]);
  69. }
  70. else {
  71. schema[i] = null;
  72. }
  73. }
  74. }
  75. }
  76. else {
  77. schema = [];
  78. }
  79. return schema;
  80. },
  81. recursiveDuckColumns: function (schema, lastCol, parent) {
  82. var prop, i;
  83. if (typeof lastCol === 'undefined') {
  84. lastCol = 0;
  85. parent = '';
  86. }
  87. if ($.isPlainObject(schema)) {
  88. for (i in schema) {
  89. if (schema.hasOwnProperty(i)) {
  90. if (schema[i] === null) {
  91. prop = parent + i;
  92. priv.colToProp.push(prop);
  93. priv.propToCol[prop] = lastCol;
  94. lastCol++;
  95. }
  96. else {
  97. lastCol = datamap.recursiveDuckColumns(schema[i], lastCol, i + '.');
  98. }
  99. }
  100. }
  101. }
  102. return lastCol;
  103. },
  104. createMap: function () {
  105. if (typeof datamap.getSchema() === "undefined") {
  106. throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`");
  107. }
  108. var i, ilen, schema = datamap.getSchema();
  109. priv.colToProp = [];
  110. priv.propToCol = {};
  111. if (priv.settings.columns) {
  112. for (i = 0, ilen = priv.settings.columns.length; i < ilen; i++) {
  113. priv.colToProp[i] = priv.settings.columns[i].data;
  114. priv.propToCol[priv.settings.columns[i].data] = i;
  115. }
  116. }
  117. else {
  118. datamap.recursiveDuckColumns(schema);
  119. }
  120. },
  121. colToProp: function (col) {
  122. col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
  123. if (priv.colToProp && typeof priv.colToProp[col] !== 'undefined') {
  124. return priv.colToProp[col];
  125. }
  126. else {
  127. return col;
  128. }
  129. },
  130. propToCol: function (prop) {
  131. var col;
  132. if (typeof priv.propToCol[prop] !== 'undefined') {
  133. col = priv.propToCol[prop];
  134. }
  135. else {
  136. col = prop;
  137. }
  138. col = Handsontable.PluginHooks.execute(instance, 'modifyCol', col);
  139. return col;
  140. },
  141. getSchema: function () {
  142. if (priv.settings.dataSchema) {
  143. if (typeof priv.settings.dataSchema === 'function') {
  144. return priv.settings.dataSchema();
  145. }
  146. return priv.settings.dataSchema;
  147. }
  148. return priv.duckDataSchema;
  149. },
  150. /**
  151. * Creates row at the bottom of the data array
  152. * @param {Number} [index] Optional. Index of the row before which the new row will be inserted
  153. */
  154. createRow: function (index) {
  155. var row
  156. , rowCount = instance.countRows();
  157. if (typeof index !== 'number' || index >= rowCount) {
  158. index = rowCount;
  159. }
  160. if (priv.dataType === 'array') {
  161. row = [];
  162. for (var c = 0, clen = instance.countCols(); c < clen; c++) {
  163. row.push(null);
  164. }
  165. }
  166. else if (priv.dataType === 'function') {
  167. row = priv.settings.dataSchema(index);
  168. }
  169. else {
  170. row = $.extend(true, {}, datamap.getSchema());
  171. }
  172. if (index === rowCount) {
  173. GridSettings.prototype.data.push(row);
  174. }
  175. else {
  176. GridSettings.prototype.data.splice(index, 0, row);
  177. }
  178. instance.PluginHooks.run('afterCreateRow', index);
  179. instance.forceFullRender = true; //used when data was changed
  180. },
  181. /**
  182. * Creates col at the right of the data array
  183. * @param {Object} [index] Optional. Index of the column before which the new column will be inserted
  184. */
  185. createCol: function (index) {
  186. if (priv.dataType === 'object' || priv.settings.columns) {
  187. throw new Error("Cannot create new column. When data source in an object, you can only have as much columns as defined in first data row, data schema or in the 'columns' setting");
  188. }
  189. var r = 0, rlen = instance.countRows()
  190. , data = GridSettings.prototype.data
  191. , constructor = Handsontable.helper.columnFactory(GridSettings, priv.columnsSettingConflicts, Handsontable.TextCell);
  192. if (typeof index !== 'number' || index >= instance.countCols()) {
  193. for (; r < rlen; r++) {
  194. if (typeof data[r] === 'undefined') {
  195. data[r] = [];
  196. }
  197. data[r].push(null);
  198. }
  199. // Add new column constructor
  200. priv.columnSettings.push(constructor);
  201. }
  202. else {
  203. for (; r < rlen; r++) {
  204. data[r].splice(index, 0, null);
  205. }
  206. // Add new column constructor at given index
  207. priv.columnSettings.splice(index, 0, constructor);
  208. }
  209. instance.PluginHooks.run('afterCreateCol', index);
  210. instance.forceFullRender = true; //used when data was changed
  211. },
  212. /**
  213. * Removes row from the data array
  214. * @param {Number} [index] Optional. Index of the row to be removed. If not provided, the last row will be removed
  215. * @param {Number} [amount] Optional. Amount of the rows to be removed. If not provided, one row will be removed
  216. */
  217. removeRow: function (index, amount) {
  218. if (!amount) {
  219. amount = 1;
  220. }
  221. if (typeof index !== 'number') {
  222. index = -amount;
  223. }
  224. GridSettings.prototype.data.splice(index, amount);
  225. instance.PluginHooks.run('afterRemoveRow', index, amount);
  226. instance.forceFullRender = true; //used when data was changed
  227. },
  228. /**
  229. * Removes column from the data array
  230. * @param {Number} [index] Optional. Index of the column to be removed. If not provided, the last column will be removed
  231. * @param {Number} [amount] Optional. Amount of the columns to be removed. If not provided, one column will be removed
  232. */
  233. removeCol: function (index, amount) {
  234. if (priv.dataType === 'object' || priv.settings.columns) {
  235. throw new Error("cannot remove column with object data source or columns option specified");
  236. }
  237. if (!amount) {
  238. amount = 1;
  239. }
  240. if (typeof index !== 'number') {
  241. index = -amount;
  242. }
  243. var data = GridSettings.prototype.data;
  244. for (var r = 0, rlen = instance.countRows(); r < rlen; r++) {
  245. data[r].splice(index, amount);
  246. }
  247. instance.PluginHooks.run('afterRemoveCol', index, amount);
  248. priv.columnSettings.splice(index, amount);
  249. instance.forceFullRender = true; //used when data was changed
  250. },
  251. /**
  252. * Add / removes data from the column
  253. * @param {Number} col Index of column in which do you want to do splice.
  254. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  255. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  256. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  257. */
  258. spliceCol: function (col, index, amount/*, elements...*/) {
  259. var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
  260. var colData = instance.getDataAtCol(col);
  261. var removed = colData.slice(index, index + amount);
  262. var after = colData.slice(index + amount);
  263. Handsontable.helper.extendArray(elements, after);
  264. var i = 0;
  265. while (i < amount) {
  266. elements.push(null); //add null in place of removed elements
  267. i++;
  268. }
  269. Handsontable.helper.to2dArray(elements);
  270. instance.populateFromArray(index, col, elements, null, null, 'spliceCol');
  271. return removed;
  272. },
  273. /**
  274. * Add / removes data from the row
  275. * @param {Number} row Index of row in which do you want to do splice.
  276. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  277. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  278. * param {...*} elements Optional. The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
  279. */
  280. spliceRow: function (row, index, amount/*, elements...*/) {
  281. var elements = 4 <= arguments.length ? [].slice.call(arguments, 3) : [];
  282. var rowData = instance.getDataAtRow(row);
  283. var removed = rowData.slice(index, index + amount);
  284. var after = rowData.slice(index + amount);
  285. Handsontable.helper.extendArray(elements, after);
  286. var i = 0;
  287. while (i < amount) {
  288. elements.push(null); //add null in place of removed elements
  289. i++;
  290. }
  291. instance.populateFromArray(row, index, [elements], null, null, 'spliceRow');
  292. return removed;
  293. },
  294. /**
  295. * Returns single value from the data array
  296. * @param {Number} row
  297. * @param {Number} prop
  298. */
  299. getVars: {},
  300. get: function (row, prop) {
  301. datamap.getVars.row = row;
  302. datamap.getVars.prop = prop;
  303. instance.PluginHooks.run('beforeGet', datamap.getVars);
  304. if (typeof datamap.getVars.prop === 'string' && datamap.getVars.prop.indexOf('.') > -1) {
  305. var sliced = datamap.getVars.prop.split(".");
  306. var out = priv.settings.data[datamap.getVars.row];
  307. if (!out) {
  308. return null;
  309. }
  310. for (var i = 0, ilen = sliced.length; i < ilen; i++) {
  311. out = out[sliced[i]];
  312. if (typeof out === 'undefined') {
  313. return null;
  314. }
  315. }
  316. return out;
  317. }
  318. else if (typeof datamap.getVars.prop === 'function') {
  319. /**
  320. * allows for interacting with complex structures, for example
  321. * d3/jQuery getter/setter properties:
  322. *
  323. * {columns: [{
  324. * data: function(row, value){
  325. * if(arguments.length === 1){
  326. * return row.property();
  327. * }
  328. * row.property(value);
  329. * }
  330. * }]}
  331. */
  332. return datamap.getVars.prop(priv.settings.data.slice(
  333. datamap.getVars.row,
  334. datamap.getVars.row + 1
  335. )[0]);
  336. }
  337. else {
  338. return priv.settings.data[datamap.getVars.row] ? priv.settings.data[datamap.getVars.row][datamap.getVars.prop] : null;
  339. }
  340. },
  341. /**
  342. * Saves single value to the data array
  343. * @param {Number} row
  344. * @param {Number} prop
  345. * @param {String} value
  346. * @param {String} [source] Optional. Source of hook runner.
  347. */
  348. setVars: {},
  349. set: function (row, prop, value, source) {
  350. datamap.setVars.row = row;
  351. datamap.setVars.prop = prop;
  352. datamap.setVars.value = value;
  353. instance.PluginHooks.run('beforeSet', datamap.setVars, source || "datamapGet");
  354. if (typeof datamap.setVars.prop === 'string' && datamap.setVars.prop.indexOf('.') > -1) {
  355. var sliced = datamap.setVars.prop.split(".");
  356. var out = priv.settings.data[datamap.setVars.row];
  357. for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) {
  358. out = out[sliced[i]];
  359. }
  360. out[sliced[i]] = datamap.setVars.value;
  361. }
  362. else if (typeof datamap.setVars.prop === 'function') {
  363. /* see the `function` handler in `get` */
  364. datamap.setVars.prop(priv.settings.data.slice(
  365. datamap.setVars.row,
  366. datamap.setVars.row + 1
  367. )[0], datamap.setVars.value);
  368. }
  369. else {
  370. priv.settings.data[datamap.setVars.row][datamap.setVars.prop] = datamap.setVars.value;
  371. }
  372. },
  373. /**
  374. * Clears the data array
  375. */
  376. clear: function () {
  377. for (var r = 0; r < instance.countRows(); r++) {
  378. for (var c = 0; c < instance.countCols(); c++) {
  379. datamap.set(r, datamap.colToProp(c), '');
  380. }
  381. }
  382. },
  383. /**
  384. * Returns the data array
  385. * @return {Array}
  386. */
  387. getAll: function () {
  388. return priv.settings.data;
  389. },
  390. /**
  391. * Returns data range as array
  392. * @param {Object} start Start selection position
  393. * @param {Object} end End selection position
  394. * @return {Array}
  395. */
  396. getRange: function (start, end) {
  397. var r, rlen, c, clen, output = [], row;
  398. rlen = Math.max(start.row, end.row);
  399. clen = Math.max(start.col, end.col);
  400. for (r = Math.min(start.row, end.row); r <= rlen; r++) {
  401. row = [];
  402. for (c = Math.min(start.col, end.col); c <= clen; c++) {
  403. row.push(datamap.get(r, datamap.colToProp(c)));
  404. }
  405. output.push(row);
  406. }
  407. return output;
  408. },
  409. /**
  410. * Return data as text (tab separated columns)
  411. * @param {Object} start (Optional) Start selection position
  412. * @param {Object} end (Optional) End selection position
  413. * @return {String}
  414. */
  415. getText: function (start, end) {
  416. return SheetClip.stringify(datamap.getRange(start, end));
  417. }
  418. };
  419. grid = {
  420. /**
  421. * Inserts or removes rows and columns
  422. * @param {String} action Possible values: "insert_row", "insert_col", "remove_row", "remove_col"
  423. * @param {Number} index
  424. * @param {Number} amount
  425. * @param {String} [source] Optional. Source of hook runner.
  426. * @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows.
  427. */
  428. alter: function (action, index, amount, source, keepEmptyRows) {
  429. var oldData, newData, changes, r, rlen, c, clen, delta;
  430. oldData = $.extend(true, [], datamap.getAll());
  431. switch (action) {
  432. case "insert_row":
  433. if (!amount) {
  434. amount = 1;
  435. }
  436. delta = 0;
  437. while (delta < amount && instance.countRows() < priv.settings.maxRows) {
  438. datamap.createRow(index);
  439. delta++;
  440. }
  441. if (delta) {
  442. if (priv.selStart.exists() && priv.selStart.row() >= index) {
  443. priv.selStart.row(priv.selStart.row() + delta);
  444. selection.transformEnd(delta, 0); //will call render() internally
  445. }
  446. else {
  447. selection.refreshBorders(); //it will call render and prepare methods
  448. }
  449. }
  450. break;
  451. case "insert_col":
  452. if (!amount) {
  453. amount = 1;
  454. }
  455. delta = 0;
  456. while (delta < amount && instance.countCols() < priv.settings.maxCols) {
  457. datamap.createCol(index);
  458. delta++;
  459. }
  460. if (delta) {
  461. if (priv.selStart.exists() && priv.selStart.col() >= index) {
  462. priv.selStart.col(priv.selStart.col() + delta);
  463. selection.transformEnd(0, delta); //will call render() internally
  464. }
  465. else {
  466. selection.refreshBorders(); //it will call render and prepare methods
  467. }
  468. }
  469. break;
  470. case "remove_row":
  471. datamap.removeRow(index, amount);
  472. grid.adjustRowsAndCols();
  473. selection.refreshBorders(); //it will call render and prepare methods
  474. break;
  475. case "remove_col":
  476. datamap.removeCol(index, amount);
  477. grid.adjustRowsAndCols();
  478. selection.refreshBorders(); //it will call render and prepare methods
  479. break;
  480. default:
  481. throw new Error('There is no such action "' + action + '"');
  482. break;
  483. }
  484. changes = [];
  485. newData = datamap.getAll();
  486. for (r = 0, rlen = newData.length; r < rlen; r++) {
  487. for (c = 0, clen = newData[r].length; c < clen; c++) {
  488. changes.push([r, c, oldData[r] ? oldData[r][c] : null, newData[r][c]]);
  489. }
  490. }
  491. instance.PluginHooks.run('afterChange', changes, source || action);
  492. if (!keepEmptyRows) {
  493. grid.adjustRowsAndCols(); //makes sure that we did not add rows that will be removed in next refresh
  494. }
  495. },
  496. /**
  497. * Makes sure there are empty rows at the bottom of the table
  498. */
  499. adjustRowsAndCols: function () {
  500. var r, rlen, emptyRows = instance.countEmptyRows(true), emptyCols;
  501. //should I add empty rows to data source to meet minRows?
  502. rlen = instance.countRows();
  503. if (rlen < priv.settings.minRows) {
  504. for (r = 0; r < priv.settings.minRows - rlen; r++) {
  505. datamap.createRow();
  506. }
  507. }
  508. //should I add empty rows to meet minSpareRows?
  509. if (emptyRows < priv.settings.minSpareRows) {
  510. for (; emptyRows < priv.settings.minSpareRows && instance.countRows() < priv.settings.maxRows; emptyRows++) {
  511. datamap.createRow();
  512. }
  513. }
  514. //count currently empty cols
  515. emptyCols = instance.countEmptyCols(true);
  516. //should I add empty cols to meet minCols?
  517. if (!priv.settings.columns && instance.countCols() < priv.settings.minCols) {
  518. for (; instance.countCols() < priv.settings.minCols; emptyCols++) {
  519. datamap.createCol();
  520. }
  521. }
  522. //should I add empty cols to meet minSpareCols?
  523. if (!priv.settings.columns && priv.dataType === 'array' && emptyCols < priv.settings.minSpareCols) {
  524. for (; emptyCols < priv.settings.minSpareCols && instance.countCols() < priv.settings.maxCols; emptyCols++) {
  525. datamap.createCol();
  526. }
  527. }
  528. if (priv.settings.enterBeginsEditing) {
  529. for (; (((priv.settings.minRows || priv.settings.minSpareRows) && instance.countRows() > priv.settings.minRows) && (priv.settings.minSpareRows && emptyRows > priv.settings.minSpareRows)); emptyRows--) {
  530. datamap.removeRow();
  531. }
  532. }
  533. if (priv.settings.enterBeginsEditing && !priv.settings.columns) {
  534. for (; (((priv.settings.minCols || priv.settings.minSpareCols) && instance.countCols() > priv.settings.minCols) && (priv.settings.minSpareCols && emptyCols > priv.settings.minSpareCols)); emptyCols--) {
  535. datamap.removeCol();
  536. }
  537. }
  538. var rowCount = instance.countRows();
  539. var colCount = instance.countCols();
  540. if (rowCount === 0 || colCount === 0) {
  541. selection.deselect();
  542. }
  543. if (priv.selStart.exists()) {
  544. var selectionChanged;
  545. var fromRow = priv.selStart.row();
  546. var fromCol = priv.selStart.col();
  547. var toRow = priv.selEnd.row();
  548. var toCol = priv.selEnd.col();
  549. //if selection is outside, move selection to last row
  550. if (fromRow > rowCount - 1) {
  551. fromRow = rowCount - 1;
  552. selectionChanged = true;
  553. if (toRow > fromRow) {
  554. toRow = fromRow;
  555. }
  556. } else if (toRow > rowCount - 1) {
  557. toRow = rowCount - 1;
  558. selectionChanged = true;
  559. if (fromRow > toRow) {
  560. fromRow = toRow;
  561. }
  562. }
  563. //if selection is outside, move selection to last row
  564. if (fromCol > colCount - 1) {
  565. fromCol = colCount - 1;
  566. selectionChanged = true;
  567. if (toCol > fromCol) {
  568. toCol = fromCol;
  569. }
  570. } else if (toCol > colCount - 1) {
  571. toCol = colCount - 1;
  572. selectionChanged = true;
  573. if (fromCol > toCol) {
  574. fromCol = toCol;
  575. }
  576. }
  577. if (selectionChanged) {
  578. instance.selectCell(fromRow, fromCol, toRow, toCol);
  579. }
  580. }
  581. },
  582. /**
  583. * Populate cells at position with 2d array
  584. * @param {Object} start Start selection position
  585. * @param {Array} input 2d array
  586. * @param {Object} [end] End selection position (only for drag-down mode)
  587. * @param {String} [source="populateFromArray"]
  588. * @param {String} [method="overwrite"]
  589. * @return {Object|undefined} ending td in pasted area (only if any cell was changed)
  590. */
  591. populateFromArray: function (start, input, end, source, method) {
  592. var r, rlen, c, clen, setData = [], current = {};
  593. rlen = input.length;
  594. if (rlen === 0) {
  595. return false;
  596. }
  597. var repeatCol
  598. , repeatRow
  599. , cmax
  600. , rmax;
  601. // insert data with specified pasteMode method
  602. switch (method) {
  603. case 'shift_down' :
  604. repeatCol = end ? end.col - start.col + 1 : 0;
  605. repeatRow = end ? end.row - start.row + 1 : 0;
  606. input = Handsontable.helper.translateRowsToColumns(input);
  607. for (c = 0, clen = input.length, cmax = Math.max(clen, repeatCol); c < cmax; c++) {
  608. if (c < clen) {
  609. for (r = 0, rlen = input[c].length; r < repeatRow - rlen; r++) {
  610. input[c].push(input[c][r % rlen]);
  611. }
  612. input[c].unshift(start.col + c, start.row, 0);
  613. instance.spliceCol.apply(instance, input[c]);
  614. }
  615. else {
  616. input[c % clen][0] = start.col + c;
  617. instance.spliceCol.apply(instance, input[c % clen]);
  618. }
  619. }
  620. break;
  621. case 'shift_right' :
  622. repeatCol = end ? end.col - start.col + 1 : 0;
  623. repeatRow = end ? end.row - start.row + 1 : 0;
  624. for (r = 0, rlen = input.length, rmax = Math.max(rlen, repeatRow); r < rmax; r++) {
  625. if (r < rlen) {
  626. for (c = 0, clen = input[r].length; c < repeatCol - clen; c++) {
  627. input[r].push(input[r][c % clen]);
  628. }
  629. input[r].unshift(start.row + r, start.col, 0);
  630. instance.spliceRow.apply(instance, input[r]);
  631. }
  632. else {
  633. input[r % rlen][0] = start.row + r;
  634. instance.spliceRow.apply(instance, input[r % rlen]);
  635. }
  636. }
  637. break;
  638. case 'overwrite' :
  639. default:
  640. // overwrite and other not specified options
  641. current.row = start.row;
  642. current.col = start.col;
  643. for (r = 0; r < rlen; r++) {
  644. if ((end && current.row > end.row) || (!priv.settings.minSpareRows && current.row > instance.countRows() - 1) || (current.row >= priv.settings.maxRows)) {
  645. break;
  646. }
  647. current.col = start.col;
  648. clen = input[r] ? input[r].length : 0;
  649. for (c = 0; c < clen; c++) {
  650. if ((end && current.col > end.col) || (!priv.settings.minSpareCols && current.col > instance.countCols() - 1) || (current.col >= priv.settings.maxCols)) {
  651. break;
  652. }
  653. if (instance.getCellMeta(current.row, current.col).isWritable) {
  654. setData.push([current.row, current.col, input[r][c]]);
  655. }
  656. current.col++;
  657. if (end && c === clen - 1) {
  658. c = -1;
  659. }
  660. }
  661. current.row++;
  662. if (end && r === rlen - 1) {
  663. r = -1;
  664. }
  665. }
  666. instance.setDataAtCell(setData, null, null, source || 'populateFromArray');
  667. break;
  668. }
  669. },
  670. /**
  671. * Returns the top left (TL) and bottom right (BR) selection coordinates
  672. * @param {Object[]} coordsArr
  673. * @returns {Object}
  674. */
  675. getCornerCoords: function (coordsArr) {
  676. function mapProp(func, array, prop) {
  677. function getProp(el) {
  678. return el[prop];
  679. }
  680. if (Array.prototype.map) {
  681. return func.apply(Math, array.map(getProp));
  682. }
  683. return func.apply(Math, $.map(array, getProp));
  684. }
  685. return {
  686. TL: {
  687. row: mapProp(Math.min, coordsArr, "row"),
  688. col: mapProp(Math.min, coordsArr, "col")
  689. },
  690. BR: {
  691. row: mapProp(Math.max, coordsArr, "row"),
  692. col: mapProp(Math.max, coordsArr, "col")
  693. }
  694. };
  695. },
  696. /**
  697. * Returns array of td objects given start and end coordinates
  698. */
  699. getCellsAtCoords: function (start, end) {
  700. var corners = grid.getCornerCoords([start, end]);
  701. var r, c, output = [];
  702. for (r = corners.TL.row; r <= corners.BR.row; r++) {
  703. for (c = corners.TL.col; c <= corners.BR.col; c++) {
  704. output.push(instance.view.getCellAtCoords({
  705. row: r,
  706. col: c
  707. }));
  708. }
  709. }
  710. return output;
  711. }
  712. };
  713. this.selection = selection = { //this public assignment is only temporary
  714. inProgress: false,
  715. /**
  716. * Sets inProgress to true. This enables onSelectionEnd and onSelectionEndByProp to function as desired
  717. */
  718. begin: function () {
  719. instance.selection.inProgress = true;
  720. },
  721. /**
  722. * Sets inProgress to false. Triggers onSelectionEnd and onSelectionEndByProp
  723. */
  724. finish: function () {
  725. var sel = instance.getSelected();
  726. instance.PluginHooks.run("afterSelectionEnd", sel[0], sel[1], sel[2], sel[3]);
  727. instance.PluginHooks.run("afterSelectionEndByProp", sel[0], instance.colToProp(sel[1]), sel[2], instance.colToProp(sel[3]));
  728. instance.selection.inProgress = false;
  729. },
  730. isInProgress: function () {
  731. return instance.selection.inProgress;
  732. },
  733. /**
  734. * Starts selection range on given td object
  735. * @param {Object} coords
  736. */
  737. setRangeStart: function (coords) {
  738. priv.selStart.coords(coords);
  739. selection.setRangeEnd(coords);
  740. },
  741. /**
  742. * Ends selection range on given td object
  743. * @param {Object} coords
  744. * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to range end
  745. */
  746. setRangeEnd: function (coords, scrollToCell) {
  747. instance.selection.begin();
  748. priv.selEnd.coords(coords);
  749. if (!priv.settings.multiSelect) {
  750. priv.selStart.coords(coords);
  751. }
  752. //set up current selection
  753. instance.view.wt.selections.current.clear();
  754. instance.view.wt.selections.current.add(priv.selStart.arr());
  755. //set up area selection
  756. instance.view.wt.selections.area.clear();
  757. if (selection.isMultiple()) {
  758. instance.view.wt.selections.area.add(priv.selStart.arr());
  759. instance.view.wt.selections.area.add(priv.selEnd.arr());
  760. }
  761. //set up highlight
  762. if (priv.settings.currentRowClassName || priv.settings.currentColClassName) {
  763. instance.view.wt.selections.highlight.clear();
  764. instance.view.wt.selections.highlight.add(priv.selStart.arr());
  765. instance.view.wt.selections.highlight.add(priv.selEnd.arr());
  766. }
  767. //trigger handlers
  768. instance.PluginHooks.run("afterSelection", priv.selStart.row(), priv.selStart.col(), priv.selEnd.row(), priv.selEnd.col());
  769. instance.PluginHooks.run("afterSelectionByProp", priv.selStart.row(), datamap.colToProp(priv.selStart.col()), priv.selEnd.row(), datamap.colToProp(priv.selEnd.col()));
  770. if (scrollToCell !== false) {
  771. instance.view.scrollViewport(coords);
  772. instance.view.wt.draw(true); //these two lines are needed to fix scrolling viewport when cell dimensions are significantly bigger than assumed by Walkontable
  773. instance.view.scrollViewport(coords);
  774. }
  775. selection.refreshBorders();
  776. },
  777. /**
  778. * Destroys editor, redraws borders around cells, prepares editor
  779. * @param {Boolean} revertOriginal
  780. * @param {Boolean} keepEditor
  781. */
  782. refreshBorders: function (revertOriginal, keepEditor) {
  783. if (!keepEditor) {
  784. editproxy.destroy(revertOriginal);
  785. }
  786. instance.view.render();
  787. if (selection.isSelected() && !keepEditor) {
  788. editproxy.prepare();
  789. }
  790. },
  791. /**
  792. * Returns information if we have a multiselection
  793. * @return {Boolean}
  794. */
  795. isMultiple: function () {
  796. return !(priv.selEnd.col() === priv.selStart.col() && priv.selEnd.row() === priv.selStart.row());
  797. },
  798. /**
  799. * Selects cell relative to current cell (if possible)
  800. */
  801. transformStart: function (rowDelta, colDelta, force) {
  802. if (priv.selStart.row() + rowDelta > instance.countRows() - 1) {
  803. if (force && priv.settings.minSpareRows > 0) {
  804. instance.alter("insert_row", instance.countRows());
  805. }
  806. else if (priv.settings.autoWrapCol && priv.selStart.col() + colDelta < instance.countCols() - 1) {
  807. rowDelta = 1 - instance.countRows();
  808. colDelta = 1;
  809. }
  810. }
  811. else if (priv.settings.autoWrapCol && priv.selStart.row() + rowDelta < 0 && priv.selStart.col() + colDelta >= 0) {
  812. rowDelta = instance.countRows() - 1;
  813. colDelta = -1;
  814. }
  815. if (priv.selStart.col() + colDelta > instance.countCols() - 1) {
  816. if (force && priv.settings.minSpareCols > 0) {
  817. instance.alter("insert_col", instance.countCols());
  818. }
  819. else if (priv.settings.autoWrapRow && priv.selStart.row() + rowDelta < instance.countRows() - 1) {
  820. rowDelta = 1;
  821. colDelta = 1 - instance.countCols();
  822. }
  823. }
  824. else if (priv.settings.autoWrapRow && priv.selStart.col() + colDelta < 0 && priv.selStart.row() + rowDelta >= 0) {
  825. rowDelta = -1;
  826. colDelta = instance.countCols() - 1;
  827. }
  828. var totalRows = instance.countRows();
  829. var totalCols = instance.countCols();
  830. var coords = {
  831. row: (priv.selStart.row() + rowDelta),
  832. col: priv.selStart.col() + colDelta
  833. };
  834. if (coords.row < 0) {
  835. coords.row = 0;
  836. }
  837. else if (coords.row > 0 && coords.row >= totalRows) {
  838. coords.row = totalRows - 1;
  839. }
  840. if (coords.col < 0) {
  841. coords.col = 0;
  842. }
  843. else if (coords.col > 0 && coords.col >= totalCols) {
  844. coords.col = totalCols - 1;
  845. }
  846. selection.setRangeStart(coords);
  847. },
  848. /**
  849. * Sets selection end cell relative to current selection end cell (if possible)
  850. */
  851. transformEnd: function (rowDelta, colDelta) {
  852. if (priv.selEnd.exists()) {
  853. var totalRows = instance.countRows();
  854. var totalCols = instance.countCols();
  855. var coords = {
  856. row: priv.selEnd.row() + rowDelta,
  857. col: priv.selEnd.col() + colDelta
  858. };
  859. if (coords.row < 0) {
  860. coords.row = 0;
  861. }
  862. else if (coords.row > 0 && coords.row >= totalRows) {
  863. coords.row = totalRows - 1;
  864. }
  865. if (coords.col < 0) {
  866. coords.col = 0;
  867. }
  868. else if (coords.col > 0 && coords.col >= totalCols) {
  869. coords.col = totalCols - 1;
  870. }
  871. selection.setRangeEnd(coords);
  872. }
  873. },
  874. /**
  875. * Returns true if currently there is a selection on screen, false otherwise
  876. * @return {Boolean}
  877. */
  878. isSelected: function () {
  879. return priv.selEnd.exists();
  880. },
  881. /**
  882. * Returns true if coords is within current selection coords
  883. * @return {Boolean}
  884. */
  885. inInSelection: function (coords) {
  886. if (!selection.isSelected()) {
  887. return false;
  888. }
  889. var sel = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
  890. return (sel.TL.row <= coords.row && sel.BR.row >= coords.row && sel.TL.col <= coords.col && sel.BR.col >= coords.col);
  891. },
  892. /**
  893. * Deselects all selected cells
  894. */
  895. deselect: function () {
  896. if (!selection.isSelected()) {
  897. return;
  898. }
  899. instance.selection.inProgress = false; //needed by HT inception
  900. priv.selEnd = new Handsontable.SelectionPoint(); //create new empty point to remove the existing one
  901. instance.view.wt.selections.current.clear();
  902. instance.view.wt.selections.area.clear();
  903. editproxy.destroy();
  904. selection.refreshBorders();
  905. instance.PluginHooks.run('afterDeselect');
  906. },
  907. /**
  908. * Select all cells
  909. */
  910. selectAll: function () {
  911. if (!priv.settings.multiSelect) {
  912. return;
  913. }
  914. selection.setRangeStart({
  915. row: 0,
  916. col: 0
  917. });
  918. selection.setRangeEnd({
  919. row: instance.countRows() - 1,
  920. col: instance.countCols() - 1
  921. }, false);
  922. },
  923. /**
  924. * Deletes data from selected cells
  925. */
  926. empty: function () {
  927. if (!selection.isSelected()) {
  928. return;
  929. }
  930. var corners = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
  931. var r, c, changes = [];
  932. for (r = corners.TL.row; r <= corners.BR.row; r++) {
  933. for (c = corners.TL.col; c <= corners.BR.col; c++) {
  934. if (instance.getCellMeta(r, c).isWritable) {
  935. changes.push([r, c, '']);
  936. }
  937. }
  938. }
  939. instance.setDataAtCell(changes);
  940. }
  941. };
  942. this.autofill = autofill = { //this public assignment is only temporary
  943. handle: null,
  944. /**
  945. * Create fill handle and fill border objects
  946. */
  947. init: function () {
  948. if (!autofill.handle) {
  949. autofill.handle = {};
  950. }
  951. else {
  952. autofill.handle.disabled = false;
  953. }
  954. },
  955. /**
  956. * Hide fill handle and fill border permanently
  957. */
  958. disable: function () {
  959. autofill.handle.disabled = true;
  960. },
  961. /**
  962. * Selects cells down to the last row in the left column, then fills down to that cell
  963. */
  964. selectAdjacent: function () {
  965. var select, data, r, maxR, c;
  966. if (selection.isMultiple()) {
  967. select = instance.view.wt.selections.area.getCorners();
  968. }
  969. else {
  970. select = instance.view.wt.selections.current.getCorners();
  971. }
  972. data = datamap.getAll();
  973. rows : for (r = select[2] + 1; r < instance.countRows(); r++) {
  974. for (c = select[1]; c <= select[3]; c++) {
  975. if (data[r][c]) {
  976. break rows;
  977. }
  978. }
  979. if (!!data[r][select[1] - 1] || !!data[r][select[3] + 1]) {
  980. maxR = r;
  981. }
  982. }
  983. if (maxR) {
  984. instance.view.wt.selections.fill.clear();
  985. instance.view.wt.selections.fill.add([select[0], select[1]]);
  986. instance.view.wt.selections.fill.add([maxR, select[3]]);
  987. autofill.apply();
  988. }
  989. },
  990. /**
  991. * Apply fill values to the area in fill border, omitting the selection border
  992. */
  993. apply: function () {
  994. var drag, select, start, end, _data;
  995. autofill.handle.isDragged = 0;
  996. drag = instance.view.wt.selections.fill.getCorners();
  997. if (!drag) {
  998. return;
  999. }
  1000. instance.view.wt.selections.fill.clear();
  1001. if (selection.isMultiple()) {
  1002. select = instance.view.wt.selections.area.getCorners();
  1003. }
  1004. else {
  1005. select = instance.view.wt.selections.current.getCorners();
  1006. }
  1007. if (drag[0] === select[0] && drag[1] < select[1]) {
  1008. start = {
  1009. row: drag[0],
  1010. col: drag[1]
  1011. };
  1012. end = {
  1013. row: drag[2],
  1014. col: select[1] - 1
  1015. };
  1016. }
  1017. else if (drag[0] === select[0] && drag[3] > select[3]) {
  1018. start = {
  1019. row: drag[0],
  1020. col: select[3] + 1
  1021. };
  1022. end = {
  1023. row: drag[2],
  1024. col: drag[3]
  1025. };
  1026. }
  1027. else if (drag[0] < select[0] && drag[1] === select[1]) {
  1028. start = {
  1029. row: drag[0],
  1030. col: drag[1]
  1031. };
  1032. end = {
  1033. row: select[0] - 1,
  1034. col: drag[3]
  1035. };
  1036. }
  1037. else if (drag[2] > select[2] && drag[1] === select[1]) {
  1038. start = {
  1039. row: select[2] + 1,
  1040. col: drag[1]
  1041. };
  1042. end = {
  1043. row: drag[2],
  1044. col: drag[3]
  1045. };
  1046. }
  1047. if (start) {
  1048. _data = SheetClip.parse(datamap.getText(priv.selStart.coords(), priv.selEnd.coords()));
  1049. instance.PluginHooks.run('beforeAutofill', start, end, _data);
  1050. grid.populateFromArray(start, _data, end, 'autofill');
  1051. selection.setRangeStart({row: drag[0], col: drag[1]});
  1052. selection.setRangeEnd({row: drag[2], col: drag[3]});
  1053. }
  1054. /*else {
  1055. //reset to avoid some range bug
  1056. selection.refreshBorders();
  1057. }*/
  1058. },
  1059. /**
  1060. * Show fill border
  1061. */
  1062. showBorder: function (coords) {
  1063. coords.row = coords[0];
  1064. coords.col = coords[1];
  1065. var corners = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
  1066. if (priv.settings.fillHandle !== 'horizontal' && (corners.BR.row < coords.row || corners.TL.row > coords.row)) {
  1067. coords = [coords.row, corners.BR.col];
  1068. }
  1069. else if (priv.settings.fillHandle !== 'vertical') {
  1070. coords = [corners.BR.row, coords.col];
  1071. }
  1072. else {
  1073. return; //wrong direction
  1074. }
  1075. instance.view.wt.selections.fill.clear();
  1076. instance.view.wt.selections.fill.add([priv.selStart.coords().row, priv.selStart.coords().col]);
  1077. instance.view.wt.selections.fill.add([priv.selEnd.coords().row, priv.selEnd.coords().col]);
  1078. instance.view.wt.selections.fill.add(coords);
  1079. instance.view.render();
  1080. }
  1081. };
  1082. editproxy = { //this public assignment is only temporary
  1083. /**
  1084. * Create input field
  1085. */
  1086. init: function () {
  1087. function onCut() {
  1088. selection.empty();
  1089. }
  1090. function onPaste(str) {
  1091. var input = str.replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, '') //remove newline from the start and the end of the input
  1092. , inputArray = SheetClip.parse(input)
  1093. , coords = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()])
  1094. , areaStart = coords.TL
  1095. , areaEnd = {
  1096. row: Math.max(coords.BR.row, inputArray.length - 1 + coords.TL.row),
  1097. col: Math.max(coords.BR.col, inputArray[0].length - 1 + coords.TL.col)
  1098. };
  1099. instance.PluginHooks.once('afterChange', function (changes, source) {
  1100. if (changes && changes.length) {
  1101. instance.selectCell(areaStart.row, areaStart.col, areaEnd.row, areaEnd.col);
  1102. }
  1103. });
  1104. grid.populateFromArray(areaStart, inputArray, areaEnd, 'paste', priv.settings.pasteMode);
  1105. }
  1106. var $body = $(document.body);
  1107. function onKeyDown(event) {
  1108. if (priv.settings.beforeOnKeyDown) { // HOT in HOT Plugin
  1109. priv.settings.beforeOnKeyDown.call(instance, event);
  1110. }
  1111. if ($body.children('.context-menu-list:visible').length) {
  1112. return;
  1113. }
  1114. if (event.keyCode === 17 || event.keyCode === 224 || event.keyCode === 91 || event.keyCode === 93) {
  1115. //when CTRL is pressed, prepare selectable text in textarea
  1116. //http://stackoverflow.com/questions/3902635/how-does-one-capture-a-macs-command-key-via-javascript
  1117. editproxy.setCopyableText();
  1118. return;
  1119. }
  1120. priv.lastKeyCode = event.keyCode;
  1121. if (selection.isSelected()) {
  1122. var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
  1123. if (Handsontable.helper.isPrintableChar(event.keyCode) && ctrlDown) {
  1124. if (event.keyCode === 65) { //CTRL + A
  1125. selection.selectAll(); //select all cells
  1126. editproxy.setCopyableText();
  1127. event.preventDefault();
  1128. }
  1129. else if (event.keyCode === 89 || (event.shiftKey && event.keyCode === 90)) { //CTRL + Y or CTRL + SHIFT + Z
  1130. priv.undoRedo && priv.undoRedo.redo();
  1131. }
  1132. else if (event.keyCode === 90) { //CTRL + Z
  1133. priv.undoRedo && priv.undoRedo.undo();
  1134. }
  1135. return;
  1136. }
  1137. var rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart;
  1138. instance.PluginHooks.run('beforeKeyDown', event);
  1139. if (!event.isImmediatePropagationStopped()) {
  1140. switch (event.keyCode) {
  1141. case 38: /* arrow up */
  1142. if (event.shiftKey) {
  1143. selection.transformEnd(-1, 0);
  1144. }
  1145. else {
  1146. selection.transformStart(-1, 0);
  1147. }
  1148. event.preventDefault();
  1149. event.stopPropagation(); //required by HandsontableEditor
  1150. break;
  1151. case 9: /* tab */
  1152. var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves;
  1153. if (event.shiftKey) {
  1154. selection.transformStart(-tabMoves.row, -tabMoves.col); //move selection left
  1155. }
  1156. else {
  1157. selection.transformStart(tabMoves.row, tabMoves.col, true); //move selection right (add a new column if needed)
  1158. }
  1159. event.preventDefault();
  1160. event.stopPropagation(); //required by HandsontableEditor
  1161. break;
  1162. case 39: /* arrow right */
  1163. if (event.shiftKey) {
  1164. selection.transformEnd(0, 1);
  1165. }
  1166. else {
  1167. selection.transformStart(0, 1);
  1168. }
  1169. event.preventDefault();
  1170. event.stopPropagation(); //required by HandsontableEditor
  1171. break;
  1172. case 37: /* arrow left */
  1173. if (event.shiftKey) {
  1174. selection.transformEnd(0, -1);
  1175. }
  1176. else {
  1177. selection.transformStart(0, -1);
  1178. }
  1179. event.preventDefault();
  1180. event.stopPropagation(); //required by HandsontableEditor
  1181. break;
  1182. case 8: /* backspace */
  1183. case 46: /* delete */
  1184. selection.empty(event);
  1185. event.preventDefault();
  1186. break;
  1187. case 40: /* arrow down */
  1188. if (event.shiftKey) {
  1189. selection.transformEnd(1, 0); //expanding selection down with shift
  1190. }
  1191. else {
  1192. selection.transformStart(1, 0); //move selection down
  1193. }
  1194. event.preventDefault();
  1195. event.stopPropagation(); //required by HandsontableEditor
  1196. break;
  1197. case 113: /* F2 */
  1198. event.preventDefault(); //prevent Opera from opening Go to Page dialog
  1199. break;
  1200. case 13: /* return/enter */
  1201. var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves;
  1202. if (event.shiftKey) {
  1203. selection.transformStart(-enterMoves.row, -enterMoves.col); //move selection up
  1204. }
  1205. else {
  1206. selection.transformStart(enterMoves.row, enterMoves.col, true); //move selection down (add a new row if needed)
  1207. }
  1208. event.preventDefault(); //don't add newline to field
  1209. break;
  1210. case 36: /* home */
  1211. if (event.ctrlKey || event.metaKey) {
  1212. rangeModifier({row: 0, col: priv.selStart.col()});
  1213. }
  1214. else {
  1215. rangeModifier({row: priv.selStart.row(), col: 0});
  1216. }
  1217. event.preventDefault(); //don't scroll the window
  1218. event.stopPropagation(); //required by HandsontableEditor
  1219. break;
  1220. case 35: /* end */
  1221. if (event.ctrlKey || event.metaKey) {
  1222. rangeModifier({row: instance.countRows() - 1, col: priv.selStart.col()});
  1223. }
  1224. else {
  1225. rangeModifier({row: priv.selStart.row(), col: instance.countCols() - 1});
  1226. }
  1227. event.preventDefault(); //don't scroll the window
  1228. event.stopPropagation(); //required by HandsontableEditor
  1229. break;
  1230. case 33: /* pg up */
  1231. selection.transformStart(-instance.countVisibleRows(), 0);
  1232. instance.view.wt.scrollVertical(-instance.countVisibleRows());
  1233. instance.view.render();
  1234. event.preventDefault(); //don't page up the window
  1235. event.stopPropagation(); //required by HandsontableEditor
  1236. break;
  1237. case 34: /* pg down */
  1238. selection.transformStart(instance.countVisibleRows(), 0);
  1239. instance.view.wt.scrollVertical(instance.countVisibleRows());
  1240. instance.view.render();
  1241. event.preventDefault(); //don't page down the window
  1242. event.stopPropagation(); //required by HandsontableEditor
  1243. break;
  1244. default:
  1245. break;
  1246. }
  1247. }
  1248. }
  1249. }
  1250. instance.copyPaste = new CopyPaste(instance.rootElement[0]);
  1251. instance.copyPaste.onCut(onCut);
  1252. instance.copyPaste.onPaste(onPaste);
  1253. instance.rootElement.on('keydown.handsontable.' + instance.guid, onKeyDown);
  1254. },
  1255. /**
  1256. * Destroy current editor, if exists
  1257. * @param {Boolean} revertOriginal
  1258. */
  1259. destroy: function (revertOriginal) {
  1260. if (typeof priv.editorDestroyer === "function") {
  1261. var destroyer = priv.editorDestroyer; //this copy is needed, otherwise destroyer can enter an infinite loop
  1262. priv.editorDestroyer = null;
  1263. destroyer(revertOriginal);
  1264. }
  1265. },
  1266. /**
  1267. * Prepares copyable text in the invisible textarea
  1268. */
  1269. setCopyableText: function () {
  1270. var startRow = Math.min(priv.selStart.row(), priv.selEnd.row());
  1271. var startCol = Math.min(priv.selStart.col(), priv.selEnd.col());
  1272. var endRow = Math.max(priv.selStart.row(), priv.selEnd.row());
  1273. var endCol = Math.max(priv.selStart.col(), priv.selEnd.col());
  1274. var finalEndRow = Math.min(endRow, startRow + priv.settings.copyRowsLimit - 1);
  1275. var finalEndCol = Math.min(endCol, startCol + priv.settings.copyColsLimit - 1);
  1276. instance.copyPaste.copyable(datamap.getText({row: startRow, col: startCol}, {row: finalEndRow, col: finalEndCol}));
  1277. if (endRow !== finalEndRow || endCol !== finalEndCol) {
  1278. instance.PluginHooks.run("afterCopyLimit", endRow - startRow + 1, endCol - startCol + 1, priv.settings.copyRowsLimit, priv.settings.copyColsLimit);
  1279. }
  1280. },
  1281. /**
  1282. * Prepare text input to be displayed at given grid cell
  1283. */
  1284. prepare: function () {
  1285. if (!instance.getCellMeta(priv.selStart.row(), priv.selStart.col()).isWritable) {
  1286. return;
  1287. }
  1288. instance.listen();
  1289. var TD = instance.view.getCellAtCoords(priv.selStart.coords());
  1290. priv.editorDestroyer = instance.view.applyCellTypeMethod('editor', TD, priv.selStart.row(), priv.selStart.col());
  1291. //presumably TD can be removed from here. Cell editor should also listen for changes if editable cell is outside from viewport
  1292. }
  1293. };
  1294. this.init = function () {
  1295. instance.PluginHooks.run('beforeInit');
  1296. editproxy.init();
  1297. this.updateSettings(priv.settings, true);
  1298. this.parseSettingsFromDOM();
  1299. this.focusCatcher = new Handsontable.FocusCatcher(this);
  1300. this.view = new Handsontable.TableView(this);
  1301. this.forceFullRender = true; //used when data was changed
  1302. this.view.render();
  1303. if (typeof priv.firstRun === 'object') {
  1304. instance.PluginHooks.run('afterChange', priv.firstRun[0], priv.firstRun[1]);
  1305. priv.firstRun = false;
  1306. }
  1307. instance.PluginHooks.run('afterInit');
  1308. };
  1309. function validateChanges(changes, source) {
  1310. var validated = $.Deferred();
  1311. var deferreds = [];
  1312. //validate strict autocompletes
  1313. var process = function (i) {
  1314. var deferred = $.Deferred();
  1315. deferreds.push(deferred);
  1316. var originalVal = changes[i][3];
  1317. var lowercaseVal = typeof originalVal === 'string' ? originalVal.toLowerCase() : null;
  1318. return function (source) {
  1319. var found = false;
  1320. for (var s = 0, slen = source.length; s < slen; s++) {
  1321. if (originalVal === source[s]) {
  1322. found = true; //perfect match
  1323. break;
  1324. }
  1325. else if (lowercaseVal === source[s].toLowerCase()) {
  1326. changes[i][3]