manualColumnMove.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. import BasePlugin from './../_base';
  2. import Hooks from './../../pluginHooks';
  3. import { arrayEach } from './../../helpers/array';
  4. import { addClass, removeClass, offset } from './../../helpers/dom/element';
  5. import { rangeEach } from './../../helpers/number';
  6. import EventManager from './../../eventManager';
  7. import { registerPlugin } from './../../plugins';
  8. import ColumnsMapper from './columnsMapper';
  9. import BacklightUI from './ui/backlight';
  10. import GuidelineUI from './ui/guideline';
  11. import './manualColumnMove.css';
  12. Hooks.getSingleton().register('beforeColumnMove');
  13. Hooks.getSingleton().register('afterColumnMove');
  14. Hooks.getSingleton().register('unmodifyCol');
  15. const privatePool = new WeakMap();
  16. const CSS_PLUGIN = 'ht__manualColumnMove';
  17. const CSS_SHOW_UI = 'show-ui';
  18. const CSS_ON_MOVING = 'on-moving--columns';
  19. const CSS_AFTER_SELECTION = 'after-selection--columns';
  20. /**
  21. * @plugin ManualColumnMove
  22. *
  23. * @description
  24. * This plugin allows to change columns order. To make columns order persistent the {@link Options#persistentState}
  25. * plugin should be enabled.
  26. *
  27. * API:
  28. * - moveColumn - move single column to the new position.
  29. * - moveColumns - move many columns (as an array of indexes) to the new position.
  30. *
  31. * If you want apply visual changes, you have to call manually the render() method on the instance of Handsontable.
  32. *
  33. * The plugin creates additional components to make moving possibly using user interface:
  34. * - backlight - highlight of selected columns.
  35. * - guideline - line which shows where rows has been moved.
  36. *
  37. * @class ManualColumnMove
  38. * @plugin ManualColumnMove
  39. */
  40. class ManualColumnMove extends BasePlugin {
  41. constructor(hotInstance) {
  42. super(hotInstance);
  43. /**
  44. * Set up WeakMap of plugin to sharing private parameters;
  45. */
  46. privatePool.set(this, {
  47. columnsToMove: [],
  48. countCols: 0,
  49. fixedColumns: 0,
  50. pressed: void 0,
  51. disallowMoving: void 0,
  52. target: {
  53. eventPageX: void 0,
  54. coords: void 0,
  55. TD: void 0,
  56. col: void 0
  57. }
  58. });
  59. /**
  60. * List of last removed row indexes.
  61. *
  62. * @private
  63. * @type {Array}
  64. */
  65. this.removedColumns = [];
  66. /**
  67. * Object containing visual row indexes mapped to data source indexes.
  68. *
  69. * @private
  70. * @type {RowsMapper}
  71. */
  72. this.columnsMapper = new ColumnsMapper(this);
  73. /**
  74. * Event Manager object.
  75. *
  76. * @private
  77. * @type {Object}
  78. */
  79. this.eventManager = new EventManager(this);
  80. /**
  81. * Backlight UI object.
  82. *
  83. * @private
  84. * @type {Object}
  85. */
  86. this.backlight = new BacklightUI(hotInstance);
  87. /**
  88. * Guideline UI object.
  89. *
  90. * @private
  91. * @type {Object}
  92. */
  93. this.guideline = new GuidelineUI(hotInstance);
  94. }
  95. /**
  96. * Checks if the plugin is enabled in the handsontable settings. This method is executed in {@link Hooks#beforeInit}
  97. * hook and if it returns `true` than the {@link ManualColumnMove#enablePlugin} method is called.
  98. *
  99. * @returns {Boolean}
  100. */
  101. isEnabled() {
  102. return !!this.hot.getSettings().manualColumnMove;
  103. }
  104. /**
  105. * Enables the plugin functionality for this Handsontable instance.
  106. */
  107. enablePlugin() {
  108. if (this.enabled) {
  109. return;
  110. }
  111. this.addHook('beforeOnCellMouseDown', (event, coords, TD, blockCalculations) => this.onBeforeOnCellMouseDown(event, coords, TD, blockCalculations));
  112. this.addHook('beforeOnCellMouseOver', (event, coords, TD, blockCalculations) => this.onBeforeOnCellMouseOver(event, coords, TD, blockCalculations));
  113. this.addHook('afterScrollVertically', () => this.onAfterScrollVertically());
  114. this.addHook('modifyCol', (row, source) => this.onModifyCol(row, source));
  115. this.addHook('beforeRemoveCol', (index, amount) => this.onBeforeRemoveCol(index, amount));
  116. this.addHook('afterRemoveCol', () => this.onAfterRemoveCol());
  117. this.addHook('afterCreateCol', (index, amount) => this.onAfterCreateCol(index, amount));
  118. this.addHook('afterLoadData', () => this.onAfterLoadData());
  119. this.addHook('unmodifyCol', column => this.onUnmodifyCol(column));
  120. this.registerEvents();
  121. // TODO: move adding plugin classname to BasePlugin.
  122. addClass(this.hot.rootElement, CSS_PLUGIN);
  123. super.enablePlugin();
  124. }
  125. /**
  126. * Updates the plugin state. This method is executed when {@link Core#updateSettings} is invoked.
  127. */
  128. updatePlugin() {
  129. this.disablePlugin();
  130. this.enablePlugin();
  131. this.onAfterPluginsInitialized();
  132. super.updatePlugin();
  133. }
  134. /**
  135. * Disables the plugin functionality for this Handsontable instance.
  136. */
  137. disablePlugin() {
  138. const pluginSettings = this.hot.getSettings().manualColumnMove;
  139. if (Array.isArray(pluginSettings)) {
  140. this.columnsMapper.clearMap();
  141. }
  142. removeClass(this.hot.rootElement, CSS_PLUGIN);
  143. this.unregisterEvents();
  144. this.backlight.destroy();
  145. this.guideline.destroy();
  146. super.disablePlugin();
  147. }
  148. /**
  149. * Moves a single column.
  150. *
  151. * @param {Number} column Visual column index to be moved.
  152. * @param {Number} target Visual column index being a target for the moved column.
  153. * @fires Hooks#beforeColumnMove
  154. * @fires Hooks#afterColumnMove
  155. */
  156. moveColumn(column, target) {
  157. this.moveColumns([column], target);
  158. }
  159. /**
  160. * Moves a multiple columns.
  161. *
  162. * @param {Array} columns Array of visual column indexes to be moved.
  163. * @param {Number} target Visual column index being a target for the moved columns.
  164. * @fires Hooks#beforeColumnMove
  165. * @fires Hooks#afterColumnMove
  166. */
  167. moveColumns(columns, target) {
  168. const visualColumns = [...columns];
  169. const priv = privatePool.get(this);
  170. const beforeColumnHook = this.hot.runHooks('beforeColumnMove', visualColumns, target);
  171. priv.disallowMoving = !beforeColumnHook;
  172. if (beforeColumnHook !== false) {
  173. // first we need to rewrite an visual indexes to physical for save reference after move
  174. arrayEach(columns, (column, index, array) => {
  175. array[index] = this.columnsMapper.getValueByIndex(column);
  176. });
  177. // next, when we have got an physical indexes, we can move columns
  178. arrayEach(columns, (column, index) => {
  179. const actualPosition = this.columnsMapper.getIndexByValue(column);
  180. if (actualPosition !== target) {
  181. this.columnsMapper.moveColumn(actualPosition, target + index);
  182. }
  183. });
  184. // after moving we have to clear columnsMapper from null entries
  185. this.columnsMapper.clearNull();
  186. }
  187. this.hot.runHooks('afterColumnMove', visualColumns, target);
  188. }
  189. /**
  190. * Correct the cell selection after the move action. Fired only when action was made with a mouse.
  191. * That means that changing the column order using the API won't correct the selection.
  192. *
  193. * @private
  194. * @param {Number} startColumn Visual column index for the start of the selection.
  195. * @param {Number} endColumn Visual column index for the end of the selection.
  196. */
  197. changeSelection(startColumn, endColumn) {
  198. this.hot.selectColumns(startColumn, endColumn);
  199. }
  200. /**
  201. * Gets the sum of the widths of columns in the provided range.
  202. *
  203. * @private
  204. * @param {Number} from Visual column index.
  205. * @param {Number} to Visual column index.
  206. * @returns {Number}
  207. */
  208. getColumnsWidth(from, to) {
  209. let width = 0;
  210. for (let i = from; i < to; i++) {
  211. let columnWidth = 0;
  212. if (i < 0) {
  213. columnWidth = this.hot.view.wt.wtViewport.getRowHeaderWidth() || 0;
  214. } else {
  215. columnWidth = this.hot.view.wt.wtTable.getStretchedColumnWidth(i) || 0;
  216. }
  217. width += columnWidth;
  218. }
  219. return width;
  220. }
  221. /**
  222. * Loads initial settings when persistent state is saved or when plugin was initialized as an array.
  223. *
  224. * @private
  225. */
  226. initialSettings() {
  227. const pluginSettings = this.hot.getSettings().manualColumnMove;
  228. if (Array.isArray(pluginSettings)) {
  229. this.moveColumns(pluginSettings, 0);
  230. } else if (pluginSettings !== void 0) {
  231. this.persistentStateLoad();
  232. }
  233. }
  234. /**
  235. * Checks if the provided column is in the fixedColumnsLeft section.
  236. *
  237. * @private
  238. * @param {Number} column Visual column index to check.
  239. * @returns {Boolean}
  240. */
  241. isFixedColumnsLeft(column) {
  242. return column < this.hot.getSettings().fixedColumnsLeft;
  243. }
  244. /**
  245. * Saves the manual column positions to the persistent state (the {@link Options#persistentState} option has to be enabled).
  246. */
  247. persistentStateSave() {
  248. this.hot.runHooks('persistentStateSave', 'manualColumnMove', this.columnsMapper._arrayMap);
  249. }
  250. /**
  251. * Loads the manual column positions from the persistent state (the {@link Options#persistentState} option has to be enabled).
  252. */
  253. persistentStateLoad() {
  254. const storedState = {};
  255. this.hot.runHooks('persistentStateLoad', 'manualColumnMove', storedState);
  256. if (storedState.value) {
  257. this.columnsMapper._arrayMap = storedState.value;
  258. }
  259. }
  260. /**
  261. * Prepares an array of indexes based on actual selection.
  262. *
  263. * @private
  264. * @returns {Array}
  265. */
  266. prepareColumnsToMoving(start, end) {
  267. const selectedColumns = [];
  268. rangeEach(start, end, (i) => {
  269. selectedColumns.push(i);
  270. });
  271. return selectedColumns;
  272. }
  273. /**
  274. * Updates the UI visual position.
  275. *
  276. * @private
  277. */
  278. refreshPositions() {
  279. const priv = privatePool.get(this);
  280. const firstVisible = this.hot.view.wt.wtTable.getFirstVisibleColumn();
  281. const lastVisible = this.hot.view.wt.wtTable.getLastVisibleColumn();
  282. const wtTable = this.hot.view.wt.wtTable;
  283. const scrollableElement = this.hot.view.wt.wtOverlays.scrollableElement;
  284. const scrollLeft = typeof scrollableElement.scrollX === 'number' ? scrollableElement.scrollX : scrollableElement.scrollLeft;
  285. let tdOffsetLeft = this.hot.view.THEAD.offsetLeft + this.getColumnsWidth(0, priv.coordsColumn);
  286. const mouseOffsetLeft = priv.target.eventPageX - (priv.rootElementOffset - (scrollableElement.scrollX === void 0 ? scrollLeft : 0));
  287. const hiderWidth = wtTable.hider.offsetWidth;
  288. const tbodyOffsetLeft = wtTable.TBODY.offsetLeft;
  289. const backlightElemMarginLeft = this.backlight.getOffset().left;
  290. const backlightElemWidth = this.backlight.getSize().width;
  291. let rowHeaderWidth = 0;
  292. if ((priv.rootElementOffset + wtTable.holder.offsetWidth + scrollLeft) < priv.target.eventPageX) {
  293. if (priv.coordsColumn < priv.countCols) {
  294. priv.coordsColumn += 1;
  295. }
  296. }
  297. if (priv.hasRowHeaders) {
  298. rowHeaderWidth = this.hot.view.wt.wtOverlays.leftOverlay.clone.wtTable.getColumnHeader(-1).offsetWidth;
  299. }
  300. if (this.isFixedColumnsLeft(priv.coordsColumn)) {
  301. tdOffsetLeft += scrollLeft;
  302. }
  303. tdOffsetLeft += rowHeaderWidth;
  304. if (priv.coordsColumn < 0) {
  305. // if hover on rowHeader
  306. if (priv.fixedColumns > 0) {
  307. priv.target.col = 0;
  308. } else {
  309. priv.target.col = firstVisible > 0 ? firstVisible - 1 : firstVisible;
  310. }
  311. } else if (((priv.target.TD.offsetWidth / 2) + tdOffsetLeft) <= mouseOffsetLeft) {
  312. const newCoordsCol = priv.coordsColumn >= priv.countCols ? priv.countCols - 1 : priv.coordsColumn;
  313. // if hover on right part of TD
  314. priv.target.col = newCoordsCol + 1;
  315. // unfortunately first column is bigger than rest
  316. tdOffsetLeft += priv.target.TD.offsetWidth;
  317. if (priv.target.col > lastVisible && lastVisible < priv.countCols) {
  318. this.hot.scrollViewportTo(void 0, lastVisible + 1, void 0, true);
  319. }
  320. } else {
  321. // elsewhere on table
  322. priv.target.col = priv.coordsColumn;
  323. if (priv.target.col <= firstVisible && priv.target.col >= priv.fixedColumns && firstVisible > 0) {
  324. this.hot.scrollViewportTo(void 0, firstVisible - 1);
  325. }
  326. }
  327. if (priv.target.col <= firstVisible && priv.target.col >= priv.fixedColumns && firstVisible > 0) {
  328. this.hot.scrollViewportTo(void 0, firstVisible - 1);
  329. }
  330. let backlightLeft = mouseOffsetLeft;
  331. let guidelineLeft = tdOffsetLeft;
  332. if (mouseOffsetLeft + backlightElemWidth + backlightElemMarginLeft >= hiderWidth) {
  333. // prevent display backlight on the right side of the table
  334. backlightLeft = hiderWidth - backlightElemWidth - backlightElemMarginLeft;
  335. } else if (mouseOffsetLeft + backlightElemMarginLeft < tbodyOffsetLeft + rowHeaderWidth) {
  336. // prevent display backlight on the left side of the table
  337. backlightLeft = tbodyOffsetLeft + rowHeaderWidth + Math.abs(backlightElemMarginLeft);
  338. }
  339. if (tdOffsetLeft >= hiderWidth - 1) {
  340. // prevent display guideline outside the table
  341. guidelineLeft = hiderWidth - 1;
  342. } else if (guidelineLeft === 0) {
  343. // guideline has got `margin-left: -1px` as default
  344. guidelineLeft = 1;
  345. } else if (scrollableElement.scrollX !== void 0 && priv.coordsColumn < priv.fixedColumns) {
  346. guidelineLeft -= ((priv.rootElementOffset <= scrollableElement.scrollX) ? priv.rootElementOffset : 0);
  347. }
  348. this.backlight.setPosition(null, backlightLeft);
  349. this.guideline.setPosition(null, guidelineLeft);
  350. }
  351. /**
  352. * This method checks arrayMap from columnsMapper and updates the columnsMapper if it's necessary.
  353. *
  354. * @private
  355. */
  356. updateColumnsMapper() {
  357. const countCols = this.hot.countSourceCols();
  358. const columnsMapperLen = this.columnsMapper._arrayMap.length;
  359. if (columnsMapperLen === 0) {
  360. this.columnsMapper.createMap(countCols || this.hot.getSettings().startCols);
  361. } else if (columnsMapperLen < countCols) {
  362. const diff = countCols - columnsMapperLen;
  363. this.columnsMapper.insertItems(columnsMapperLen, diff);
  364. } else if (columnsMapperLen > countCols) {
  365. const maxIndex = countCols - 1;
  366. const columnsToRemove = [];
  367. arrayEach(this.columnsMapper._arrayMap, (value, index) => {
  368. if (value > maxIndex) {
  369. columnsToRemove.push(index);
  370. }
  371. });
  372. this.columnsMapper.removeItems(columnsToRemove);
  373. }
  374. }
  375. /**
  376. * Binds the events used by the plugin.
  377. *
  378. * @private
  379. */
  380. registerEvents() {
  381. this.eventManager.addEventListener(document.documentElement, 'mousemove', event => this.onMouseMove(event));
  382. this.eventManager.addEventListener(document.documentElement, 'mouseup', () => this.onMouseUp());
  383. }
  384. /**
  385. * Unbinds the events used by the plugin.
  386. *
  387. * @private
  388. */
  389. unregisterEvents() {
  390. this.eventManager.clear();
  391. }
  392. /**
  393. * Changes the behavior of selection / dragging.
  394. *
  395. * @private
  396. * @param {MouseEvent} event `mousedown` event properties.
  397. * @param {CellCoords} coords Visual cell coordinates where was fired event.
  398. * @param {HTMLElement} TD Cell represented as HTMLElement.
  399. * @param {Object} blockCalculations Object which contains information about blockCalculation for row, column or cells.
  400. */
  401. onBeforeOnCellMouseDown(event, coords, TD, blockCalculations) {
  402. const wtTable = this.hot.view.wt.wtTable;
  403. const isHeaderSelection = this.hot.selection.isSelectedByColumnHeader();
  404. const selection = this.hot.getSelectedRangeLast();
  405. const priv = privatePool.get(this);
  406. // This block action shouldn't be handled below.
  407. const isSortingElement = event.realTarget.className.indexOf('sortAction') > -1;
  408. if (!selection || !isHeaderSelection || priv.pressed || event.button !== 0 || isSortingElement) {
  409. priv.pressed = false;
  410. priv.columnsToMove.length = 0;
  411. removeClass(this.hot.rootElement, [CSS_ON_MOVING, CSS_SHOW_UI]);
  412. return;
  413. }
  414. const guidelineIsNotReady = this.guideline.isBuilt() && !this.guideline.isAppended();
  415. const backlightIsNotReady = this.backlight.isBuilt() && !this.backlight.isAppended();
  416. if (guidelineIsNotReady && backlightIsNotReady) {
  417. this.guideline.appendTo(wtTable.hider);
  418. this.backlight.appendTo(wtTable.hider);
  419. }
  420. const { from, to } = selection;
  421. const start = Math.min(from.col, to.col);
  422. const end = Math.max(from.col, to.col);
  423. if (coords.row < 0 && (coords.col >= start && coords.col <= end)) {
  424. blockCalculations.column = true;
  425. priv.pressed = true;
  426. priv.target.eventPageX = event.pageX;
  427. priv.coordsColumn = coords.col;
  428. priv.target.TD = TD;
  429. priv.target.col = coords.col;
  430. priv.columnsToMove = this.prepareColumnsToMoving(start, end);
  431. priv.hasRowHeaders = !!this.hot.getSettings().rowHeaders;
  432. priv.countCols = this.hot.countCols();
  433. priv.fixedColumns = this.hot.getSettings().fixedColumnsLeft;
  434. priv.rootElementOffset = offset(this.hot.rootElement).left;
  435. const countColumnsFrom = priv.hasRowHeaders ? -1 : 0;
  436. const topPos = wtTable.holder.scrollTop + wtTable.getColumnHeaderHeight(0) + 1;
  437. const fixedColumns = coords.col < priv.fixedColumns;
  438. const scrollableElement = this.hot.view.wt.wtOverlays.scrollableElement;
  439. const wrapperIsWindow = scrollableElement.scrollX ? scrollableElement.scrollX - priv.rootElementOffset : 0;
  440. const mouseOffset = event.layerX - (fixedColumns ? wrapperIsWindow : 0);
  441. const leftOffset = Math.abs(this.getColumnsWidth(start, coords.col) + mouseOffset);
  442. this.backlight.setPosition(topPos, this.getColumnsWidth(countColumnsFrom, start) + leftOffset);
  443. this.backlight.setSize(this.getColumnsWidth(start, end + 1), wtTable.hider.offsetHeight - topPos);
  444. this.backlight.setOffset(null, leftOffset * -1);
  445. addClass(this.hot.rootElement, CSS_ON_MOVING);
  446. } else {
  447. removeClass(this.hot.rootElement, CSS_AFTER_SELECTION);
  448. priv.pressed = false;
  449. priv.columnsToMove.length = 0;
  450. }
  451. }
  452. /**
  453. * 'mouseMove' event callback. Fired when pointer move on document.documentElement.
  454. *
  455. * @private
  456. * @param {MouseEvent} event `mousemove` event properties.
  457. */
  458. onMouseMove(event) {
  459. const priv = privatePool.get(this);
  460. if (!priv.pressed) {
  461. return;
  462. }
  463. // callback for browser which doesn't supports CSS pointer-event: none
  464. if (event.realTarget === this.backlight.element) {
  465. const width = this.backlight.getSize().width;
  466. this.backlight.setSize(0);
  467. setTimeout(function() {
  468. this.backlight.setPosition(width);
  469. });
  470. }
  471. priv.target.eventPageX = event.pageX;
  472. this.refreshPositions();
  473. }
  474. /**
  475. * 'beforeOnCellMouseOver' hook callback. Fired when pointer was over cell.
  476. *
  477. * @private
  478. * @param {MouseEvent} event `mouseover` event properties.
  479. * @param {CellCoords} coords Visual cell coordinates where was fired event.
  480. * @param {HTMLElement} TD Cell represented as HTMLElement.
  481. * @param {Object} blockCalculations Object which contains information about blockCalculation for row, column or cells.
  482. */
  483. onBeforeOnCellMouseOver(event, coords, TD, blockCalculations) {
  484. const selectedRange = this.hot.getSelectedRangeLast();
  485. const priv = privatePool.get(this);
  486. if (!selectedRange || !priv.pressed) {
  487. return;
  488. }
  489. if (priv.columnsToMove.indexOf(coords.col) > -1) {
  490. removeClass(this.hot.rootElement, CSS_SHOW_UI);
  491. } else {
  492. addClass(this.hot.rootElement, CSS_SHOW_UI);
  493. }
  494. blockCalculations.row = true;
  495. blockCalculations.column = true;
  496. blockCalculations.cell = true;
  497. priv.coordsColumn = coords.col;
  498. priv.target.TD = TD;
  499. }
  500. /**
  501. * `onMouseUp` hook callback.
  502. *
  503. * @private
  504. */
  505. onMouseUp() {
  506. const priv = privatePool.get(this);
  507. priv.coordsColumn = void 0;
  508. priv.pressed = false;
  509. priv.backlightWidth = 0;
  510. removeClass(this.hot.rootElement, [CSS_ON_MOVING, CSS_SHOW_UI, CSS_AFTER_SELECTION]);
  511. if (this.hot.selection.isSelectedByColumnHeader()) {
  512. addClass(this.hot.rootElement, CSS_AFTER_SELECTION);
  513. }
  514. if (priv.columnsToMove.length < 1 || priv.target.col === void 0 || priv.columnsToMove.indexOf(priv.target.col) > -1) {
  515. return;
  516. }
  517. this.moveColumns(priv.columnsToMove, priv.target.col);
  518. this.persistentStateSave();
  519. this.hot.render();
  520. this.hot.view.wt.wtOverlays.adjustElementsSize(true);
  521. if (!priv.disallowMoving) {
  522. const selectionStart = this.columnsMapper.getIndexByValue(priv.columnsToMove[0]);
  523. const selectionEnd = this.columnsMapper.getIndexByValue(priv.columnsToMove[priv.columnsToMove.length - 1]);
  524. this.changeSelection(selectionStart, selectionEnd);
  525. }
  526. priv.columnsToMove.length = 0;
  527. }
  528. /**
  529. * `afterScrollHorizontally` hook callback. Fired the table was scrolled horizontally.
  530. *
  531. * @private
  532. */
  533. onAfterScrollVertically() {
  534. const wtTable = this.hot.view.wt.wtTable;
  535. const headerHeight = wtTable.getColumnHeaderHeight(0) + 1;
  536. const scrollTop = wtTable.holder.scrollTop;
  537. const posTop = headerHeight + scrollTop;
  538. this.backlight.setPosition(posTop);
  539. this.backlight.setSize(null, wtTable.hider.offsetHeight - posTop);
  540. }
  541. /**
  542. * `afterCreateCol` hook callback.
  543. *
  544. * @private
  545. * @param {Number} index Visual index of the created column.
  546. * @param {Number} amount Amount of created columns.
  547. */
  548. onAfterCreateCol(index, amount) {
  549. this.columnsMapper.shiftItems(index, amount);
  550. }
  551. /**
  552. * On before remove column listener.
  553. *
  554. * @private
  555. * @param {Number} index Visual column index.
  556. * @param {Number} amount Defines how many columns removed.
  557. */
  558. onBeforeRemoveCol(index, amount) {
  559. this.removedColumns.length = 0;
  560. if (index !== false) {
  561. // Collect physical row index.
  562. rangeEach(index, index + amount - 1, (removedIndex) => {
  563. this.removedColumns.push(this.hot.runHooks('modifyCol', removedIndex, this.pluginName));
  564. });
  565. }
  566. }
  567. /**
  568. * `afterRemoveCol` hook callback.
  569. *
  570. * @private
  571. */
  572. onAfterRemoveCol() {
  573. this.columnsMapper.unshiftItems(this.removedColumns);
  574. }
  575. /**
  576. * `afterLoadData` hook callback.
  577. *
  578. * @private
  579. */
  580. onAfterLoadData() {
  581. this.updateColumnsMapper();
  582. }
  583. /**
  584. * 'modifyRow' hook callback.
  585. *
  586. * @private
  587. * @param {Number} column Visual column index.
  588. * @returns {Number} Physical column index.
  589. */
  590. onModifyCol(column, source) {
  591. let physicalColumn = column;
  592. if (source !== this.pluginName) {
  593. // ugly fix for try to insert new, needed columns after pasting data
  594. const columnInMapper = this.columnsMapper.getValueByIndex(physicalColumn);
  595. physicalColumn = columnInMapper === null ? physicalColumn : columnInMapper;
  596. }
  597. return physicalColumn;
  598. }
  599. /**
  600. * 'unmodifyCol' hook callback.
  601. *
  602. * @private
  603. * @param {Number} column Physical column index.
  604. * @returns {Number} Visual column index.
  605. */
  606. onUnmodifyCol(column) {
  607. const indexInMapper = this.columnsMapper.getIndexByValue(column);
  608. return indexInMapper === null ? column : indexInMapper;
  609. }
  610. /**
  611. * `afterPluginsInitialized` hook callback.
  612. *
  613. * @private
  614. */
  615. onAfterPluginsInitialized() {
  616. this.updateColumnsMapper();
  617. this.initialSettings();
  618. this.backlight.build();
  619. this.guideline.build();
  620. }
  621. /**
  622. * Destroys the plugin instance.
  623. */
  624. destroy() {
  625. this.backlight.destroy();
  626. this.guideline.destroy();
  627. super.destroy();
  628. }
  629. }
  630. registerPlugin('ManualColumnMove', ManualColumnMove);
  631. export default ManualColumnMove;