b1e78d636236a8ae73857eb0c2d36fafe74f4c73c76d8b7d79e6cb69ff417dd334ea33711f0763147473242d1757e54b87cabc0ef5e777850953621e337e30 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. import SheetClip from './../lib/SheetClip/SheetClip.js';
  2. import {cellMethodLookupFactory} from './helpers/data';
  3. import {columnFactory} from './helpers/setting';
  4. import {createObjectPropListener, duckSchema, deepExtend, deepClone, isObject, deepObjectSize, hasOwnProperty} from './helpers/object';
  5. import {extendArray, to2dArray} from './helpers/array';
  6. import Interval from './utils/interval';
  7. import {rangeEach} from './helpers/number';
  8. import MultiMap from './multiMap';
  9. import Hooks from './pluginHooks';
  10. /**
  11. * Utility class that gets and saves data from/to the data source using mapping of columns numbers to object property names
  12. * @todo refactor arguments of methods getRange, getText to be numbers (not objects)
  13. * @todo remove priv, GridSettings from object constructor
  14. *
  15. * @param {Object} instance Instance of Handsontable
  16. * @param {*} priv
  17. * @param {*} GridSettings Grid settings
  18. * @util
  19. * @class DataMap
  20. */
  21. function DataMap(instance, priv, GridSettings) {
  22. this.instance = instance;
  23. this.priv = priv;
  24. this.GridSettings = GridSettings;
  25. this.dataSource = this.instance.getSettings().data;
  26. this.cachedLength = null;
  27. this.skipCache = false;
  28. this.latestSourceRowsCount = 0;
  29. if (this.dataSource && this.dataSource[0]) {
  30. this.duckSchema = this.recursiveDuckSchema(this.dataSource[0]);
  31. } else {
  32. this.duckSchema = {};
  33. }
  34. this.createMap();
  35. this.interval = Interval.create(() => this.clearLengthCache(), '15fps');
  36. this.instance.addHook('skipLengthCache', (delay) => this.onSkipLengthCache(delay));
  37. this.onSkipLengthCache(500);
  38. }
  39. DataMap.prototype.DESTINATION_RENDERER = 1;
  40. DataMap.prototype.DESTINATION_CLIPBOARD_GENERATOR = 2;
  41. /**
  42. * @param {Object|Array} object
  43. * @returns {Object|Array}
  44. */
  45. DataMap.prototype.recursiveDuckSchema = function(object) {
  46. return duckSchema(object);
  47. };
  48. /**
  49. * @param {Object} schema
  50. * @param {Number} lastCol
  51. * @param {Number} parent
  52. * @returns {Number}
  53. */
  54. DataMap.prototype.recursiveDuckColumns = function(schema, lastCol, parent) {
  55. var prop,
  56. i;
  57. if (typeof lastCol === 'undefined') {
  58. lastCol = 0;
  59. parent = '';
  60. }
  61. if (typeof schema === 'object' && !Array.isArray(schema)) {
  62. for (i in schema) {
  63. if (hasOwnProperty(schema, i)) {
  64. if (schema[i] === null) {
  65. prop = parent + i;
  66. this.colToPropCache.push(prop);
  67. this.propToColCache.set(prop, lastCol);
  68. lastCol++;
  69. } else {
  70. lastCol = this.recursiveDuckColumns(schema[i], lastCol, `${i}.`);
  71. }
  72. }
  73. }
  74. }
  75. return lastCol;
  76. };
  77. DataMap.prototype.createMap = function() {
  78. let i;
  79. let schema = this.getSchema();
  80. if (typeof schema === 'undefined') {
  81. throw new Error('trying to create `columns` definition but you didn\'t provide `schema` nor `data`');
  82. }
  83. this.colToPropCache = [];
  84. this.propToColCache = new MultiMap();
  85. let columns = this.instance.getSettings().columns;
  86. if (columns) {
  87. const maxCols = this.instance.getSettings().maxCols;
  88. let columnsLen = Math.min(maxCols, columns.length);
  89. let filteredIndex = 0;
  90. let columnsAsFunc = false;
  91. let schemaLen = deepObjectSize(schema);
  92. if (typeof columns === 'function') {
  93. columnsLen = schemaLen > 0 ? schemaLen : this.instance.countSourceCols();
  94. columnsAsFunc = true;
  95. }
  96. for (i = 0; i < columnsLen; i++) {
  97. let column = columnsAsFunc ? columns(i) : columns[i];
  98. if (isObject(column)) {
  99. if (typeof column.data !== 'undefined') {
  100. let index = columnsAsFunc ? filteredIndex : i;
  101. this.colToPropCache[index] = column.data;
  102. this.propToColCache.set(column.data, index);
  103. }
  104. filteredIndex++;
  105. }
  106. }
  107. } else {
  108. this.recursiveDuckColumns(schema);
  109. }
  110. };
  111. /**
  112. * Returns property name that corresponds with the given column index.
  113. *
  114. * @param {Number} col
  115. * @returns {Number}
  116. */
  117. DataMap.prototype.colToProp = function(col) {
  118. col = this.instance.runHooks('modifyCol', col);
  119. if (!isNaN(col) && this.colToPropCache && typeof this.colToPropCache[col] !== 'undefined') {
  120. return this.colToPropCache[col];
  121. }
  122. return col;
  123. };
  124. /**
  125. * @param {Object} prop
  126. * @fires Hooks#modifyCol
  127. * @returns {*}
  128. */
  129. DataMap.prototype.propToCol = function(prop) {
  130. var col;
  131. if (typeof this.propToColCache.get(prop) === 'undefined') {
  132. col = prop;
  133. } else {
  134. col = this.propToColCache.get(prop);
  135. }
  136. col = this.instance.runHooks('unmodifyCol', col);
  137. return col;
  138. };
  139. /**
  140. * @returns {Object}
  141. */
  142. DataMap.prototype.getSchema = function() {
  143. var schema = this.instance.getSettings().dataSchema;
  144. if (schema) {
  145. if (typeof schema === 'function') {
  146. return schema();
  147. }
  148. return schema;
  149. }
  150. return this.duckSchema;
  151. };
  152. /**
  153. * Creates row at the bottom of the data array.
  154. *
  155. * @param {Number} [index] Index of the row before which the new row will be inserted.
  156. * @param {Number} [amount] An amount of rows to add.
  157. * @param {String} [source] Source of method call.
  158. * @fires Hooks#afterCreateRow
  159. * @returns {Number} Returns number of created rows.
  160. */
  161. DataMap.prototype.createRow = function(index, amount, source) {
  162. var row,
  163. colCount = this.instance.countCols(),
  164. numberOfCreatedRows = 0,
  165. currentIndex;
  166. if (!amount) {
  167. amount = 1;
  168. }
  169. if (typeof index !== 'number' || index >= this.instance.countSourceRows()) {
  170. index = this.instance.countSourceRows();
  171. }
  172. this.instance.runHooks('beforeCreateRow', index, amount, source);
  173. currentIndex = index;
  174. var maxRows = this.instance.getSettings().maxRows;
  175. while (numberOfCreatedRows < amount && this.instance.countSourceRows() < maxRows) {
  176. if (this.instance.dataType === 'array') {
  177. if (this.instance.getSettings().dataSchema) {
  178. // Clone template array
  179. row = deepClone(this.getSchema());
  180. } else {
  181. row = [];
  182. /* eslint-disable no-loop-func */
  183. rangeEach(colCount - 1, () => row.push(null));
  184. }
  185. } else if (this.instance.dataType === 'function') {
  186. row = this.instance.getSettings().dataSchema(index);
  187. } else {
  188. row = {};
  189. deepExtend(row, this.getSchema());
  190. }
  191. if (index === this.instance.countSourceRows()) {
  192. this.dataSource.push(row);
  193. } else {
  194. this.spliceData(index, 0, row);
  195. }
  196. numberOfCreatedRows++;
  197. currentIndex++;
  198. }
  199. this.instance.runHooks('afterCreateRow', index, numberOfCreatedRows, source);
  200. this.instance.forceFullRender = true; // used when data was changed
  201. return numberOfCreatedRows;
  202. };
  203. /**
  204. * Creates col at the right of the data array.
  205. *
  206. * @param {Number} [index] Index of the column before which the new column will be inserted
  207. * @param {Number} [amount] An amount of columns to add.
  208. * @param {String} [source] Source of method call.
  209. * @fires Hooks#afterCreateCol
  210. * @returns {Number} Returns number of created columns
  211. */
  212. DataMap.prototype.createCol = function(index, amount, source) {
  213. if (!this.instance.isColumnModificationAllowed()) {
  214. throw new Error('Cannot create new column. When data source in an object, ' +
  215. 'you can only have as much columns as defined in first data row, data schema or in the \'columns\' setting.' +
  216. 'If you want to be able to add new columns, you have to use array datasource.');
  217. }
  218. var rlen = this.instance.countSourceRows(),
  219. data = this.dataSource,
  220. constructor,
  221. numberOfCreatedCols = 0,
  222. currentIndex;
  223. if (!amount) {
  224. amount = 1;
  225. }
  226. if (typeof index !== 'number' || index >= this.instance.countCols()) {
  227. index = this.instance.countCols();
  228. }
  229. this.instance.runHooks('beforeCreateCol', index, amount, source);
  230. currentIndex = index;
  231. var maxCols = this.instance.getSettings().maxCols;
  232. while (numberOfCreatedCols < amount && this.instance.countCols() < maxCols) {
  233. constructor = columnFactory(this.GridSettings, this.priv.columnsSettingConflicts);
  234. if (typeof index !== 'number' || index >= this.instance.countCols()) {
  235. if (rlen > 0) {
  236. for (var r = 0; r < rlen; r++) {
  237. if (typeof data[r] === 'undefined') {
  238. data[r] = [];
  239. }
  240. data[r].push(null);
  241. }
  242. } else {
  243. data.push([null]);
  244. }
  245. // Add new column constructor
  246. this.priv.columnSettings.push(constructor);
  247. } else {
  248. for (let r = 0; r < rlen; r++) {
  249. data[r].splice(currentIndex, 0, null);
  250. }
  251. // Add new column constructor at given index
  252. this.priv.columnSettings.splice(currentIndex, 0, constructor);
  253. }
  254. numberOfCreatedCols++;
  255. currentIndex++;
  256. }
  257. this.instance.runHooks('afterCreateCol', index, numberOfCreatedCols, source);
  258. this.instance.forceFullRender = true; // used when data was changed
  259. return numberOfCreatedCols;
  260. };
  261. /**
  262. * Removes row from the data array.
  263. *
  264. * @param {Number} [index] Index of the row to be removed. If not provided, the last row will be removed
  265. * @param {Number} [amount] Amount of the rows to be removed. If not provided, one row will be removed
  266. * @param {String} [source] Source of method call.
  267. * @fires Hooks#beforeRemoveRow
  268. * @fires Hooks#afterRemoveRow
  269. */
  270. DataMap.prototype.removeRow = function(index, amount, source) {
  271. if (!amount) {
  272. amount = 1;
  273. }
  274. if (typeof index !== 'number') {
  275. index = -amount;
  276. }
  277. amount = this.instance.runHooks('modifyRemovedAmount', amount, index);
  278. index = (this.instance.countSourceRows() + index) % this.instance.countSourceRows();
  279. let logicRows = this.physicalRowsToLogical(index, amount);
  280. let actionWasNotCancelled = this.instance.runHooks('beforeRemoveRow', index, amount, logicRows, source);
  281. if (actionWasNotCancelled === false) {
  282. return;
  283. }
  284. let data = this.dataSource;
  285. let newData;
  286. newData = this.filterData(index, amount);
  287. if (newData) {
  288. data.length = 0;
  289. Array.prototype.push.apply(data, newData);
  290. }
  291. this.instance.runHooks('afterRemoveRow', index, amount, logicRows, source);
  292. this.instance.forceFullRender = true; // used when data was changed
  293. };
  294. /**
  295. * Removes column from the data array.
  296. *
  297. * @param {Number} [index] Index of the column to be removed. If not provided, the last column will be removed
  298. * @param {Number} [amount] Amount of the columns to be removed. If not provided, one column will be removed
  299. * @param {String} [source] Source of method call.
  300. * @fires Hooks#beforeRemoveCol
  301. * @fires Hooks#afterRemoveCol
  302. */
  303. DataMap.prototype.removeCol = function(index, amount, source) {
  304. if (this.instance.dataType === 'object' || this.instance.getSettings().columns) {
  305. throw new Error('cannot remove column with object data source or columns option specified');
  306. }
  307. if (!amount) {
  308. amount = 1;
  309. }
  310. if (typeof index !== 'number') {
  311. index = -amount;
  312. }
  313. index = (this.instance.countCols() + index) % this.instance.countCols();
  314. let logicColumns = this.physicalColumnsToLogical(index, amount);
  315. let descendingLogicColumns = logicColumns.slice(0).sort((a, b) => b - a);
  316. let actionWasNotCancelled = this.instance.runHooks('beforeRemoveCol', index, amount, logicColumns, source);
  317. if (actionWasNotCancelled === false) {
  318. return;
  319. }
  320. let isTableUniform = true;
  321. let removedColumnsCount = descendingLogicColumns.length;
  322. let data = this.dataSource;
  323. for (let c = 0; c < removedColumnsCount; c++) {
  324. if (isTableUniform && logicColumns[0] !== logicColumns[c] - c) {
  325. isTableUniform = false;
  326. }
  327. }
  328. if (isTableUniform) {
  329. for (let r = 0, rlen = this.instance.countSourceRows(); r < rlen; r++) {
  330. data[r].splice(logicColumns[0], amount);
  331. }
  332. } else {
  333. for (let r = 0, rlen = this.instance.countSourceRows(); r < rlen; r++) {
  334. for (let c = 0; c < removedColumnsCount; c++) {
  335. data[r].splice(descendingLogicColumns[c], 1);
  336. }
  337. }
  338. for (let c = 0; c < removedColumnsCount; c++) {
  339. this.priv.columnSettings.splice(logicColumns[c], 1);
  340. }
  341. }
  342. this.instance.runHooks('afterRemoveCol', index, amount, logicColumns, source);
  343. this.instance.forceFullRender = true; // used when data was changed
  344. };
  345. /**
  346. * Add/Removes data from the column.
  347. *
  348. * @param {Number} col Index of column in which do you want to do splice
  349. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  350. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  351. * @returns {Array} Returns removed portion of columns
  352. */
  353. DataMap.prototype.spliceCol = function(col, index, amount/* , elements...*/) {
  354. var elements = arguments.length >= 4 ? [].slice.call(arguments, 3) : [];
  355. var colData = this.instance.getDataAtCol(col);
  356. var removed = colData.slice(index, index + amount);
  357. var after = colData.slice(index + amount);
  358. extendArray(elements, after);
  359. var i = 0;
  360. while (i < amount) {
  361. elements.push(null); // add null in place of removed elements
  362. i++;
  363. }
  364. to2dArray(elements);
  365. this.instance.populateFromArray(index, col, elements, null, null, 'spliceCol');
  366. return removed;
  367. };
  368. /**
  369. * Add/Removes data from the row.
  370. *
  371. * @param {Number} row Index of row in which do you want to do splice
  372. * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
  373. * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
  374. * @returns {Array} Returns removed portion of rows
  375. */
  376. DataMap.prototype.spliceRow = function(row, index, amount/* , elements...*/) {
  377. var elements = arguments.length >= 4 ? [].slice.call(arguments, 3) : [];
  378. var rowData = this.instance.getSourceDataAtRow(row);
  379. var removed = rowData.slice(index, index + amount);
  380. var after = rowData.slice(index + amount);
  381. extendArray(elements, after);
  382. var i = 0;
  383. while (i < amount) {
  384. elements.push(null); // add null in place of removed elements
  385. i++;
  386. }
  387. this.instance.populateFromArray(row, index, [elements], null, null, 'spliceRow');
  388. return removed;
  389. };
  390. /**
  391. * Add/remove row(s) to/from the data source.
  392. *
  393. * @param {Number} index Index of the element to remove.
  394. * @param {Number} amount Number of rows to add/remove.
  395. * @param {Object} element Row to add.
  396. */
  397. DataMap.prototype.spliceData = function(index, amount, element) {
  398. let continueSplicing = this.instance.runHooks('beforeDataSplice', index, amount, element);
  399. if (continueSplicing !== false) {
  400. this.dataSource.splice(index, amount, element);
  401. }
  402. };
  403. /**
  404. * Filter unwanted data elements from the data source.
  405. *
  406. * @param {Number} index Index of the element to remove.
  407. * @param {Number} amount Number of rows to add/remove.
  408. * @returns {Array}
  409. */
  410. DataMap.prototype.filterData = function(index, amount) {
  411. let logicRows = this.physicalRowsToLogical(index, amount);
  412. let continueSplicing = this.instance.runHooks('beforeDataFilter', index, amount, logicRows);
  413. if (continueSplicing !== false) {
  414. let newData = this.dataSource.filter((row, index) => logicRows.indexOf(index) == -1);
  415. return newData;
  416. }
  417. };
  418. /**
  419. * Returns single value from the data array.
  420. *
  421. * @param {Number} row
  422. * @param {Number} prop
  423. */
  424. DataMap.prototype.get = function(row, prop) {
  425. row = this.instance.runHooks('modifyRow', row);
  426. let dataRow = this.dataSource[row];
  427. // TODO: To remove, use 'modifyData' hook instead (see below)
  428. let modifiedRowData = this.instance.runHooks('modifyRowData', row);
  429. dataRow = isNaN(modifiedRowData) ? modifiedRowData : dataRow;
  430. //
  431. let value = null;
  432. // try to get value under property `prop` (includes dot)
  433. if (dataRow && dataRow.hasOwnProperty && hasOwnProperty(dataRow, prop)) {
  434. value = dataRow[prop];
  435. } else if (typeof prop === 'string' && prop.indexOf('.') > -1) {
  436. let sliced = prop.split('.');
  437. let out = dataRow;
  438. if (!out) {
  439. return null;
  440. }
  441. for (let i = 0, ilen = sliced.length; i < ilen; i++) {
  442. out = out[sliced[i]];
  443. if (typeof out === 'undefined') {
  444. return null;
  445. }
  446. }
  447. value = out;
  448. } else if (typeof prop === 'function') {
  449. /**
  450. * allows for interacting with complex structures, for example
  451. * d3/jQuery getter/setter properties:
  452. *
  453. * {columns: [{
  454. * data: function(row, value){
  455. * if(arguments.length === 1){
  456. * return row.property();
  457. * }
  458. * row.property(value);
  459. * }
  460. * }]}
  461. */
  462. value = prop(this.dataSource.slice(row, row + 1)[0]);
  463. }
  464. if (this.instance.hasHook('modifyData')) {
  465. const valueHolder = createObjectPropListener(value);
  466. this.instance.runHooks('modifyData', row, this.propToCol(prop), valueHolder, 'get');
  467. if (valueHolder.isTouched()) {
  468. value = valueHolder.value;
  469. }
  470. }
  471. return value;
  472. };
  473. var copyableLookup = cellMethodLookupFactory('copyable', false);
  474. /**
  475. * Returns single value from the data array (intended for clipboard copy to an external application).
  476. *
  477. * @param {Number} row
  478. * @param {Number} prop
  479. * @returns {String}
  480. */
  481. DataMap.prototype.getCopyable = function(row, prop) {
  482. if (copyableLookup.call(this.instance, row, this.propToCol(prop))) {
  483. return this.get(row, prop);
  484. }
  485. return '';
  486. };
  487. /**
  488. * Saves single value to the data array.
  489. *
  490. * @param {Number} row
  491. * @param {Number} prop
  492. * @param {String} value
  493. * @param {String} [source] Source of hook runner.
  494. */
  495. DataMap.prototype.set = function(row, prop, value, source) {
  496. row = this.instance.runHooks('modifyRow', row, source || 'datamapGet');
  497. let dataRow = this.dataSource[row];
  498. // TODO: To remove, use 'modifyData' hook instead (see below)
  499. let modifiedRowData = this.instance.runHooks('modifyRowData', row);
  500. dataRow = isNaN(modifiedRowData) ? modifiedRowData : dataRow;
  501. //
  502. if (this.instance.hasHook('modifyData')) {
  503. const valueHolder = createObjectPropListener(value);
  504. this.instance.runHooks('modifyData', row, this.propToCol(prop), valueHolder, 'set');
  505. if (valueHolder.isTouched()) {
  506. value = valueHolder.value;
  507. }
  508. }
  509. // try to set value under property `prop` (includes dot)
  510. if (dataRow && dataRow.hasOwnProperty && hasOwnProperty(dataRow, prop)) {
  511. dataRow[prop] = value;
  512. } else if (typeof prop === 'string' && prop.indexOf('.') > -1) {
  513. let sliced = prop.split('.');
  514. let out = dataRow;
  515. let i = 0;
  516. let ilen;
  517. for (i = 0, ilen = sliced.length - 1; i < ilen; i++) {
  518. if (typeof out[sliced[i]] === 'undefined') {
  519. out[sliced[i]] = {};
  520. }
  521. out = out[sliced[i]];
  522. }
  523. out[sliced[i]] = value;
  524. } else if (typeof prop === 'function') {
  525. /* see the `function` handler in `get` */
  526. prop(this.dataSource.slice(row, row + 1)[0], value);
  527. } else {
  528. dataRow[prop] = value;
  529. }
  530. };
  531. /**
  532. * This ridiculous piece of code maps rows Id that are present in table data to those displayed for user.
  533. * The trick is, the physical row id (stored in settings.data) is not necessary the same
  534. * as the logical (displayed) row id (e.g. when sorting is applied).
  535. *
  536. * @param {Number} index
  537. * @param {Number} amount
  538. * @fires Hooks#modifyRow
  539. * @returns {Number}
  540. */
  541. DataMap.prototype.physicalRowsToLogical = function(index, amount) {
  542. var totalRows = this.instance.countSourceRows();
  543. var physicRow = (totalRows + index) % totalRows;
  544. var logicRows = [];
  545. var rowsToRemove = amount;
  546. var row;
  547. while (physicRow < totalRows && rowsToRemove) {
  548. row = this.instance.runHooks('modifyRow', physicRow);
  549. logicRows.push(row);
  550. rowsToRemove--;
  551. physicRow++;
  552. }
  553. return logicRows;
  554. };
  555. /**
  556. *
  557. * @param index
  558. * @param amount
  559. * @returns {Array}
  560. */
  561. DataMap.prototype.physicalColumnsToLogical = function(index, amount) {
  562. let totalCols = this.instance.countCols();
  563. let physicalCol = (totalCols + index) % totalCols;
  564. let logicalCols = [];
  565. let colsToRemove = amount;
  566. while (physicalCol < totalCols && colsToRemove) {
  567. let col = this.instance.runHooks('modifyCol', physicalCol);
  568. logicalCols.push(col);
  569. colsToRemove--;
  570. physicalCol++;
  571. }
  572. return logicalCols;
  573. };
  574. /**
  575. * Clears the data array.
  576. */
  577. DataMap.prototype.clear = function() {
  578. for (var r = 0; r < this.instance.countSourceRows(); r++) {
  579. for (var c = 0; c < this.instance.countCols(); c++) {
  580. this.set(r, this.colToProp(c), '');
  581. }
  582. }
  583. };
  584. /**
  585. * Clear cached data length.
  586. */
  587. DataMap.prototype.clearLengthCache = function() {
  588. this.cachedLength = null;
  589. };
  590. /**
  591. * Get data length.
  592. *
  593. * @returns {Number}
  594. */
  595. DataMap.prototype.getLength = function() {
  596. let maxRows,
  597. maxRowsFromSettings = this.instance.getSettings().maxRows;
  598. if (maxRowsFromSettings < 0 || maxRowsFromSettings === 0) {
  599. maxRows = 0;
  600. } else {
  601. maxRows = maxRowsFromSettings || Infinity;
  602. }
  603. let length = this.instance.countSourceRows();
  604. if (this.instance.hasHook('modifyRow')) {
  605. let reValidate = this.skipCache;
  606. this.interval.start();
  607. if (length !== this.latestSourceRowsCount) {
  608. reValidate = true;
  609. }
  610. this.latestSourceRowsCount = length;
  611. if (this.cachedLength === null || reValidate) {
  612. rangeEach(length - 1, (row) => {
  613. row = this.instance.runHooks('modifyRow', row);
  614. if (row === null) {
  615. --length;
  616. }
  617. });
  618. this.cachedLength = length;
  619. } else {
  620. length = this.cachedLength;
  621. }
  622. } else {
  623. this.interval.stop();
  624. }
  625. return Math.min(length, maxRows);
  626. };
  627. /**
  628. * Returns the data array.
  629. *
  630. * @returns {Array}
  631. */
  632. DataMap.prototype.getAll = function() {
  633. const start = {
  634. row: 0,
  635. col: 0,
  636. };
  637. let end = {
  638. row: Math.max(this.instance.countSourceRows() - 1, 0),
  639. col: Math.max(this.instance.countCols() - 1, 0),
  640. };
  641. if (start.row - end.row === 0 && !this.instance.countSourceRows()) {
  642. return [];
  643. }
  644. return this.getRange(start, end, DataMap.prototype.DESTINATION_RENDERER);
  645. };
  646. /**
  647. * Returns data range as array.
  648. *
  649. * @param {Object} [start] Start selection position
  650. * @param {Object} [end] End selection position
  651. * @param {Number} destination Destination of datamap.get
  652. * @returns {Array}
  653. */
  654. DataMap.prototype.getRange = function(start, end, destination) {
  655. var r,
  656. rlen,
  657. c,
  658. clen,
  659. output = [],
  660. row;
  661. const maxRows = this.instance.getSettings().maxRows;
  662. const maxCols = this.instance.getSettings().maxCols;
  663. if (maxRows === 0 || maxCols === 0) {
  664. return [];
  665. }
  666. var getFn = destination === this.DESTINATION_CLIPBOARD_GENERATOR ? this.getCopyable : this.get;
  667. rlen = Math.min(Math.max(maxRows - 1, 0), Math.max(start.row, end.row));
  668. clen = Math.min(Math.max(maxCols - 1, 0), Math.max(start.col, end.col));
  669. for (r = Math.min(start.row, end.row); r <= rlen; r++) {
  670. row = [];
  671. let physicalRow = this.instance.runHooks('modifyRow', r);
  672. for (c = Math.min(start.col, end.col); c <= clen; c++) {
  673. if (physicalRow === null) {
  674. break;
  675. }
  676. row.push(getFn.call(this, r, this.colToProp(c)));
  677. }
  678. if (physicalRow !== null) {
  679. output.push(row);
  680. }
  681. }
  682. return output;
  683. };
  684. /**
  685. * Return data as text (tab separated columns).
  686. *
  687. * @param {Object} [start] Start selection position
  688. * @param {Object} [end] End selection position
  689. * @returns {String}
  690. */
  691. DataMap.prototype.getText = function(start, end) {
  692. return SheetClip.stringify(this.getRange(start, end, this.DESTINATION_RENDERER));
  693. };
  694. /**
  695. * Return data as copyable text (tab separated columns intended for clipboard copy to an external application).
  696. *
  697. * @param {Object} [start] Start selection position
  698. * @param {Object} [end] End selection position
  699. * @returns {String}
  700. */
  701. DataMap.prototype.getCopyableText = function(start, end) {
  702. return SheetClip.stringify(this.getRange(start, end, this.DESTINATION_CLIPBOARD_GENERATOR));
  703. };
  704. /**
  705. * `skipLengthCache` callback.
  706. * @private
  707. * @param {Number} delay Time of the delay in milliseconds.
  708. */
  709. DataMap.prototype.onSkipLengthCache = function(delay) {
  710. this.skipCache = true;
  711. setTimeout(() => {
  712. this.skipCache = false;
  713. }, delay);
  714. };
  715. /**
  716. * Destroy instance.
  717. */
  718. DataMap.prototype.destroy = function() {
  719. this.interval.stop();
  720. this.interval = null;
  721. this.instance = null;
  722. this.priv = null;
  723. this.GridSettings = null;
  724. this.dataSource = null;
  725. this.cachedLength = null;
  726. this.duckSchema = null;
  727. };
  728. export default DataMap;