autofill.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. 'use strict';
  2. exports.__esModule = true;
  3. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  4. var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
  5. var _base = require('./../_base');
  6. var _base2 = _interopRequireDefault(_base);
  7. var _pluginHooks = require('./../../pluginHooks');
  8. var _pluginHooks2 = _interopRequireDefault(_pluginHooks);
  9. var _element = require('./../../helpers/dom/element');
  10. var _eventManager = require('./../../eventManager');
  11. var _eventManager2 = _interopRequireDefault(_eventManager);
  12. var _plugins = require('./../../plugins');
  13. var _src = require('./../../3rdparty/walkontable/src');
  14. var _utils = require('./utils');
  15. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  16. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  17. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  18. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
  19. _pluginHooks2.default.getSingleton().register('modifyAutofillRange');
  20. _pluginHooks2.default.getSingleton().register('beforeAutofill');
  21. var INSERT_ROW_ALTER_ACTION_NAME = 'insert_row';
  22. var INTERVAL_FOR_ADDING_ROW = 200;
  23. /**
  24. * This plugin provides "drag-down" and "copy-down" functionalities, both operated
  25. * using the small square in the right bottom of the cell selection.
  26. *
  27. * "Drag-down" expands the value of the selected cells to the neighbouring
  28. * cells when you drag the small square in the corner.
  29. *
  30. * "Copy-down" copies the value of the selection to all empty cells
  31. * below when you double click the small square.
  32. *
  33. * @class Autofill
  34. * @plugin Autofill
  35. */
  36. var Autofill = function (_BasePlugin) {
  37. _inherits(Autofill, _BasePlugin);
  38. function Autofill(hotInstance) {
  39. _classCallCheck(this, Autofill);
  40. /**
  41. * Event manager
  42. *
  43. * @type {EventManager}
  44. */
  45. var _this = _possibleConstructorReturn(this, (Autofill.__proto__ || Object.getPrototypeOf(Autofill)).call(this, hotInstance));
  46. _this.eventManager = new _eventManager2.default(_this);
  47. /**
  48. * Specifies if adding new row started.
  49. *
  50. * @type {Boolean}
  51. */
  52. _this.addingStarted = false;
  53. /**
  54. * Specifies if there was mouse down on the cell corner.
  55. *
  56. * @type {Boolean}
  57. */
  58. _this.mouseDownOnCellCorner = false;
  59. /**
  60. * Specifies if mouse was dragged outside Handsontable.
  61. *
  62. * @type {Boolean}
  63. */
  64. _this.mouseDragOutside = false;
  65. /**
  66. * Specifies how many cell levels were dragged using the handle.
  67. *
  68. * @type {Boolean}
  69. */
  70. _this.handleDraggedCells = 0;
  71. /**
  72. * Specifies allowed directions of drag.
  73. *
  74. * @type {Array}
  75. */
  76. _this.directions = [];
  77. /**
  78. * Specifies if can insert new rows if needed.
  79. *
  80. * @type {Boolean}
  81. */
  82. _this.autoInsertRow = false;
  83. return _this;
  84. }
  85. /**
  86. * Check if the plugin is enabled in the Handsontable settings.
  87. *
  88. * @returns {Boolean}
  89. */
  90. _createClass(Autofill, [{
  91. key: 'isEnabled',
  92. value: function isEnabled() {
  93. return this.hot.getSettings().fillHandle;
  94. }
  95. /**
  96. * Enable plugin for this Handsontable instance.
  97. */
  98. }, {
  99. key: 'enablePlugin',
  100. value: function enablePlugin() {
  101. var _this2 = this;
  102. if (this.enabled) {
  103. return;
  104. }
  105. this.mapSettings();
  106. this.registerEvents();
  107. this.addHook('afterOnCellCornerMouseDown', function (event) {
  108. return _this2.onAfterCellCornerMouseDown(event);
  109. });
  110. this.addHook('afterOnCellCornerDblClick', function (event) {
  111. return _this2.onCellCornerDblClick(event);
  112. });
  113. this.addHook('beforeOnCellMouseOver', function (event, coords, TD) {
  114. return _this2.onBeforeCellMouseOver(coords);
  115. });
  116. _get(Autofill.prototype.__proto__ || Object.getPrototypeOf(Autofill.prototype), 'enablePlugin', this).call(this);
  117. }
  118. /**
  119. * Update plugin for this Handsontable instance.
  120. */
  121. }, {
  122. key: 'updatePlugin',
  123. value: function updatePlugin() {
  124. this.disablePlugin();
  125. this.enablePlugin();
  126. _get(Autofill.prototype.__proto__ || Object.getPrototypeOf(Autofill.prototype), 'updatePlugin', this).call(this);
  127. }
  128. /**
  129. * Disable plugin for this Handsontable instance.
  130. */
  131. }, {
  132. key: 'disablePlugin',
  133. value: function disablePlugin() {
  134. this.clearMappedSettings();
  135. _get(Autofill.prototype.__proto__ || Object.getPrototypeOf(Autofill.prototype), 'disablePlugin', this).call(this);
  136. }
  137. /**
  138. * Get selection data
  139. *
  140. * @private
  141. * @returns {Array} Array with the data.
  142. */
  143. }, {
  144. key: 'getSelectionData',
  145. value: function getSelectionData() {
  146. var selRange = {
  147. from: this.hot.getSelectedRange().from,
  148. to: this.hot.getSelectedRange().to
  149. };
  150. return this.hot.getData(selRange.from.row, selRange.from.col, selRange.to.row, selRange.to.col);
  151. }
  152. /**
  153. * Try to apply fill values to the area in fill border, omitting the selection border.
  154. *
  155. * @private
  156. * @returns {Boolean} reports if fill was applied.
  157. */
  158. }, {
  159. key: 'fillIn',
  160. value: function fillIn() {
  161. if (this.hot.view.wt.selections.fill.isEmpty()) {
  162. return false;
  163. }
  164. var cornersOfSelectionAndDragAreas = this.hot.view.wt.selections.fill.getCorners();
  165. this.resetSelectionOfDraggedArea();
  166. var cornersOfSelectedCells = this.getCornersOfSelectedCells();
  167. var _getDragDirectionAndR = (0, _utils.getDragDirectionAndRange)(cornersOfSelectedCells, cornersOfSelectionAndDragAreas),
  168. directionOfDrag = _getDragDirectionAndR.directionOfDrag,
  169. startOfDragCoords = _getDragDirectionAndR.startOfDragCoords,
  170. endOfDragCoords = _getDragDirectionAndR.endOfDragCoords;
  171. this.hot.runHooks('modifyAutofillRange', cornersOfSelectedCells, cornersOfSelectionAndDragAreas);
  172. if (startOfDragCoords && startOfDragCoords.row > -1 && startOfDragCoords.col > -1) {
  173. var selectionData = this.getSelectionData();
  174. var deltas = (0, _utils.getDeltas)(startOfDragCoords, endOfDragCoords, selectionData, directionOfDrag);
  175. this.hot.runHooks('beforeAutofill', startOfDragCoords, endOfDragCoords, selectionData);
  176. this.hot.populateFromArray(startOfDragCoords.row, startOfDragCoords.col, selectionData, endOfDragCoords.row, endOfDragCoords.col, this.pluginName + '.fill', null, directionOfDrag, deltas);
  177. this.setSelection(cornersOfSelectionAndDragAreas);
  178. } else {
  179. // reset to avoid some range bug
  180. this.hot.selection.refreshBorders();
  181. }
  182. return true;
  183. }
  184. /**
  185. * Reduce the selection area if the handle was dragged outside of the table or on headers.
  186. *
  187. * @private
  188. * @param {CellCoords} coords indexes of selection corners.
  189. * @returns {CellCoords}
  190. */
  191. }, {
  192. key: 'reduceSelectionAreaIfNeeded',
  193. value: function reduceSelectionAreaIfNeeded(coords) {
  194. if (coords.row < 0) {
  195. coords.row = 0;
  196. }
  197. if (coords.col < 0) {
  198. coords.col = 0;
  199. }
  200. return coords;
  201. }
  202. /**
  203. * Get the coordinates of the drag & drop borders.
  204. *
  205. * @private
  206. * @param {CellCoords} coordsOfSelection `CellCoords` coord object.
  207. * @returns {Array}
  208. */
  209. }, {
  210. key: 'getCoordsOfDragAndDropBorders',
  211. value: function getCoordsOfDragAndDropBorders(coordsOfSelection) {
  212. var topLeftCorner = this.hot.getSelectedRange().getTopLeftCorner();
  213. var bottomRightCorner = this.hot.getSelectedRange().getBottomRightCorner();
  214. var coords = void 0;
  215. if (this.directions.includes(_utils.DIRECTIONS.vertical) && (bottomRightCorner.row < coordsOfSelection.row || topLeftCorner.row > coordsOfSelection.row)) {
  216. coords = new _src.CellCoords(coordsOfSelection.row, bottomRightCorner.col);
  217. } else if (this.directions.includes(_utils.DIRECTIONS.horizontal)) {
  218. coords = new _src.CellCoords(bottomRightCorner.row, coordsOfSelection.col);
  219. } else {
  220. // wrong direction
  221. return;
  222. }
  223. return this.reduceSelectionAreaIfNeeded(coords);
  224. }
  225. /**
  226. * Show the fill border.
  227. *
  228. * @private
  229. * @param {CellCoords} coordsOfSelection `CellCoords` coord object.
  230. */
  231. }, {
  232. key: 'showBorder',
  233. value: function showBorder(coordsOfSelection) {
  234. var coordsOfDragAndDropBorders = this.getCoordsOfDragAndDropBorders(coordsOfSelection);
  235. if (coordsOfDragAndDropBorders) {
  236. this.redrawBorders(coordsOfDragAndDropBorders);
  237. }
  238. }
  239. /**
  240. * Add new row
  241. *
  242. * @private
  243. */
  244. }, {
  245. key: 'addRow',
  246. value: function addRow() {
  247. var _this3 = this;
  248. this.hot._registerTimeout(setTimeout(function () {
  249. _this3.hot.alter(INSERT_ROW_ALTER_ACTION_NAME, void 0, 1, _this3.pluginName + '.fill');
  250. _this3.addingStarted = false;
  251. }, INTERVAL_FOR_ADDING_ROW));
  252. }
  253. /**
  254. * Add new rows if they are needed to continue auto-filling values.
  255. *
  256. * @private
  257. */
  258. }, {
  259. key: 'addNewRowIfNeeded',
  260. value: function addNewRowIfNeeded() {
  261. if (this.hot.view.wt.selections.fill.cellRange && this.addingStarted === false && this.autoInsertRow) {
  262. var cornersOfSelectedCells = this.hot.getSelected();
  263. var cornersOfSelectedDragArea = this.hot.view.wt.selections.fill.getCorners();
  264. var nrOfTableRows = this.hot.countRows();
  265. if (cornersOfSelectedCells[2] < nrOfTableRows - 1 && cornersOfSelectedDragArea[2] === nrOfTableRows - 1) {
  266. this.addingStarted = true;
  267. this.addRow();
  268. }
  269. }
  270. }
  271. /**
  272. * Get corners of selected cells.
  273. *
  274. * @private
  275. * @returns {Array}
  276. */
  277. }, {
  278. key: 'getCornersOfSelectedCells',
  279. value: function getCornersOfSelectedCells() {
  280. if (this.hot.selection.isMultiple()) {
  281. return this.hot.view.wt.selections.area.getCorners();
  282. }
  283. return this.hot.view.wt.selections.current.getCorners();
  284. }
  285. /**
  286. * Get index of last adjacent filled in row
  287. *
  288. * @private
  289. * @param {Array} cornersOfSelectedCells indexes of selection corners.
  290. * @returns {Number} gives number greater than or equal to zero when selection adjacent can be applied.
  291. * or -1 when selection adjacent can't be applied
  292. */
  293. }, {
  294. key: 'getIndexOfLastAdjacentFilledInRow',
  295. value: function getIndexOfLastAdjacentFilledInRow(cornersOfSelectedCells) {
  296. var data = this.hot.getData();
  297. var nrOfTableRows = this.hot.countRows();
  298. var lastFilledInRowIndex = void 0;
  299. for (var rowIndex = cornersOfSelectedCells[2] + 1; rowIndex < nrOfTableRows; rowIndex++) {
  300. for (var columnIndex = cornersOfSelectedCells[1]; columnIndex <= cornersOfSelectedCells[3]; columnIndex++) {
  301. var dataInCell = data[rowIndex][columnIndex];
  302. if (dataInCell) {
  303. return -1;
  304. }
  305. }
  306. var dataInNextLeftCell = data[rowIndex][cornersOfSelectedCells[1] - 1];
  307. var dataInNextRightCell = data[rowIndex][cornersOfSelectedCells[3] + 1];
  308. if (!!dataInNextLeftCell || !!dataInNextRightCell) {
  309. lastFilledInRowIndex = rowIndex;
  310. }
  311. }
  312. return lastFilledInRowIndex;
  313. }
  314. /**
  315. * Add a selection from the start area to the specific row index.
  316. *
  317. * @private
  318. * @param {Array} selectStartArea selection area from which we start to create more comprehensive selection.
  319. * @param {Number} rowIndex
  320. */
  321. }, {
  322. key: 'addSelectionFromStartAreaToSpecificRowIndex',
  323. value: function addSelectionFromStartAreaToSpecificRowIndex(selectStartArea, rowIndex) {
  324. this.hot.view.wt.selections.fill.clear();
  325. this.hot.view.wt.selections.fill.add(new _src.CellCoords(selectStartArea[0], selectStartArea[1]));
  326. this.hot.view.wt.selections.fill.add(new _src.CellCoords(rowIndex, selectStartArea[3]));
  327. }
  328. /**
  329. * Set selection based on passed corners.
  330. *
  331. * @private
  332. * @param {Array} cornersOfArea
  333. */
  334. }, {
  335. key: 'setSelection',
  336. value: function setSelection(cornersOfArea) {
  337. this.hot.selection.setRangeStart(new _src.CellCoords(cornersOfArea[0], cornersOfArea[1]));
  338. this.hot.selection.setRangeEnd(new _src.CellCoords(cornersOfArea[2], cornersOfArea[3]));
  339. }
  340. /**
  341. * Try to select cells down to the last row in the left column and then returns if selection was applied.
  342. *
  343. * @private
  344. * @returns {Boolean}
  345. */
  346. }, {
  347. key: 'selectAdjacent',
  348. value: function selectAdjacent() {
  349. var cornersOfSelectedCells = this.getCornersOfSelectedCells();
  350. var lastFilledInRowIndex = this.getIndexOfLastAdjacentFilledInRow(cornersOfSelectedCells);
  351. if (lastFilledInRowIndex === -1) {
  352. return false;
  353. }
  354. this.addSelectionFromStartAreaToSpecificRowIndex(cornersOfSelectedCells, lastFilledInRowIndex);
  355. return true;
  356. }
  357. /**
  358. * Reset selection of dragged area.
  359. *
  360. * @private
  361. */
  362. }, {
  363. key: 'resetSelectionOfDraggedArea',
  364. value: function resetSelectionOfDraggedArea() {
  365. this.handleDraggedCells = 0;
  366. this.hot.view.wt.selections.fill.clear();
  367. }
  368. /**
  369. * Redraw borders.
  370. *
  371. * @private
  372. * @param {CellCoords} coords `CellCoords` coord object.
  373. */
  374. }, {
  375. key: 'redrawBorders',
  376. value: function redrawBorders(coords) {
  377. this.hot.view.wt.selections.fill.clear();
  378. this.hot.view.wt.selections.fill.add(this.hot.getSelectedRange().from);
  379. this.hot.view.wt.selections.fill.add(this.hot.getSelectedRange().to);
  380. this.hot.view.wt.selections.fill.add(coords);
  381. this.hot.view.render();
  382. }
  383. /**
  384. * Get if mouse was dragged outside.
  385. *
  386. * @private
  387. * @param {MouseEvent} event `mousemove` event properties.
  388. * @returns {Boolean}
  389. */
  390. }, {
  391. key: 'getIfMouseWasDraggedOutside',
  392. value: function getIfMouseWasDraggedOutside(event) {
  393. var tableBottom = (0, _element.offset)(this.hot.table).top - (window.pageYOffset || document.documentElement.scrollTop) + (0, _element.outerHeight)(this.hot.table);
  394. var tableRight = (0, _element.offset)(this.hot.table).left - (window.pageXOffset || document.documentElement.scrollLeft) + (0, _element.outerWidth)(this.hot.table);
  395. return event.clientY > tableBottom && event.clientX <= tableRight;
  396. }
  397. /**
  398. * Bind the events used by the plugin.
  399. *
  400. * @private
  401. */
  402. }, {
  403. key: 'registerEvents',
  404. value: function registerEvents() {
  405. var _this4 = this;
  406. this.eventManager.addEventListener(document.documentElement, 'mouseup', function () {
  407. return _this4.onMouseUp();
  408. });
  409. this.eventManager.addEventListener(document.documentElement, 'mousemove', function (event) {
  410. return _this4.onMouseMove(event);
  411. });
  412. }
  413. /**
  414. * On cell corner double click callback.
  415. *
  416. * @private
  417. */
  418. }, {
  419. key: 'onCellCornerDblClick',
  420. value: function onCellCornerDblClick() {
  421. var selectionApplied = this.selectAdjacent();
  422. if (selectionApplied) {
  423. this.fillIn();
  424. }
  425. }
  426. /**
  427. * On after cell corner mouse down listener.
  428. *
  429. * @private
  430. */
  431. }, {
  432. key: 'onAfterCellCornerMouseDown',
  433. value: function onAfterCellCornerMouseDown() {
  434. this.handleDraggedCells = 1;
  435. this.mouseDownOnCellCorner = true;
  436. }
  437. /**
  438. * On before cell mouse over listener.
  439. *
  440. * @private
  441. * @param {CellCoords} coords `CellCoords` coord object.
  442. */
  443. }, {
  444. key: 'onBeforeCellMouseOver',
  445. value: function onBeforeCellMouseOver(coords) {
  446. if (this.mouseDownOnCellCorner && !this.hot.view.isMouseDown() && this.handleDraggedCells) {
  447. this.handleDraggedCells++;
  448. this.showBorder(coords);
  449. this.addNewRowIfNeeded();
  450. }
  451. }
  452. /**
  453. * On mouse up listener.
  454. *
  455. * @private
  456. */
  457. }, {
  458. key: 'onMouseUp',
  459. value: function onMouseUp() {
  460. if (this.handleDraggedCells) {
  461. if (this.handleDraggedCells > 1) {
  462. this.fillIn();
  463. }
  464. this.handleDraggedCells = 0;
  465. this.mouseDownOnCellCorner = false;
  466. }
  467. }
  468. /**
  469. * On mouse move listener.
  470. *
  471. * @private
  472. * @param {MouseEvent} event `mousemove` event properties.
  473. */
  474. }, {
  475. key: 'onMouseMove',
  476. value: function onMouseMove(event) {
  477. var mouseWasDraggedOutside = this.getIfMouseWasDraggedOutside(event);
  478. if (this.addingStarted === false && this.handleDraggedCells > 0 && mouseWasDraggedOutside) {
  479. this.mouseDragOutside = true;
  480. this.addingStarted = true;
  481. } else {
  482. this.mouseDragOutside = false;
  483. }
  484. if (this.mouseDragOutside && this.autoInsertRow) {
  485. this.addRow();
  486. }
  487. }
  488. /**
  489. * Clear mapped settings.
  490. *
  491. * @private
  492. */
  493. }, {
  494. key: 'clearMappedSettings',
  495. value: function clearMappedSettings() {
  496. this.directions.length = 0;
  497. this.autoInsertRow = false;
  498. }
  499. /**
  500. * Map settings.
  501. *
  502. * @private
  503. */
  504. }, {
  505. key: 'mapSettings',
  506. value: function mapSettings() {
  507. var mappedSettings = (0, _utils.getMappedFillHandleSetting)(this.hot.getSettings().fillHandle);
  508. this.directions = mappedSettings.directions;
  509. this.autoInsertRow = mappedSettings.autoInsertRow;
  510. }
  511. /**
  512. * Destroy plugin instance.
  513. */
  514. }, {
  515. key: 'destroy',
  516. value: function destroy() {
  517. _get(Autofill.prototype.__proto__ || Object.getPrototypeOf(Autofill.prototype), 'destroy', this).call(this);
  518. }
  519. }]);
  520. return Autofill;
  521. }(_base2.default);
  522. (0, _plugins.registerPlugin)('autofill', Autofill);
  523. exports.default = Autofill;