058c2f6354ff601de5b91298ae4d8b1f75ce61a8ea61872c6bcbc40458ce135ef45f16e6476a6532d24007c314067c1fc25a3b43c0ae704064f47242ad5881 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. import {
  2. addClass,
  3. empty,
  4. fastInnerHTML,
  5. fastInnerText,
  6. getComputedStyle,
  7. getScrollbarWidth,
  8. hasClass,
  9. isChildOf,
  10. isInput,
  11. isOutsideInput
  12. } from './helpers/dom/element';
  13. import {isChrome, isSafari} from './helpers/browser';
  14. import EventManager from './eventManager';
  15. import {stopPropagation, isImmediatePropagationStopped, isRightClick, isLeftClick} from './helpers/dom/event';
  16. import Walkontable, {CellCoords, Selection} from './3rdparty/walkontable/src';
  17. /**
  18. * Handsontable TableView constructor
  19. * @param {Object} instance
  20. */
  21. function TableView(instance) {
  22. var that = this;
  23. this.eventManager = new EventManager(instance);
  24. this.instance = instance;
  25. this.settings = instance.getSettings();
  26. this.selectionMouseDown = false;
  27. var originalStyle = instance.rootElement.getAttribute('style');
  28. if (originalStyle) {
  29. instance.rootElement.setAttribute('data-originalstyle', originalStyle); // needed to retrieve original style in jsFiddle link generator in HT examples. may be removed in future versions
  30. }
  31. addClass(instance.rootElement, 'handsontable');
  32. var table = document.createElement('TABLE');
  33. addClass(table, 'htCore');
  34. if (instance.getSettings().tableClassName) {
  35. addClass(table, instance.getSettings().tableClassName);
  36. }
  37. this.THEAD = document.createElement('THEAD');
  38. table.appendChild(this.THEAD);
  39. this.TBODY = document.createElement('TBODY');
  40. table.appendChild(this.TBODY);
  41. instance.table = table;
  42. instance.container.insertBefore(table, instance.container.firstChild);
  43. this.eventManager.addEventListener(instance.rootElement, 'mousedown', function(event) {
  44. this.selectionMouseDown = true;
  45. if (!that.isTextSelectionAllowed(event.target)) {
  46. clearTextSelection();
  47. event.preventDefault();
  48. window.focus(); // make sure that window that contains HOT is active. Important when HOT is in iframe.
  49. }
  50. });
  51. this.eventManager.addEventListener(instance.rootElement, 'mouseup', function(event) {
  52. this.selectionMouseDown = false;
  53. });
  54. this.eventManager.addEventListener(instance.rootElement, 'mousemove', function(event) {
  55. if (this.selectionMouseDown && !that.isTextSelectionAllowed(event.target)) {
  56. clearTextSelection();
  57. event.preventDefault();
  58. }
  59. });
  60. this.eventManager.addEventListener(document.documentElement, 'keyup', function(event) {
  61. if (instance.selection.isInProgress() && !event.shiftKey) {
  62. instance.selection.finish();
  63. }
  64. });
  65. var isMouseDown;
  66. this.isMouseDown = function() {
  67. return isMouseDown;
  68. };
  69. this.eventManager.addEventListener(document.documentElement, 'mouseup', function(event) {
  70. if (instance.selection.isInProgress() && event.which === 1) { // is left mouse button
  71. instance.selection.finish();
  72. }
  73. isMouseDown = false;
  74. if (isOutsideInput(document.activeElement)) {
  75. instance.unlisten();
  76. }
  77. });
  78. this.eventManager.addEventListener(document.documentElement, 'mousedown', function(event) {
  79. var originalTarget = event.target;
  80. var next = event.target;
  81. var eventX = event.x || event.clientX;
  82. var eventY = event.y || event.clientY;
  83. if (isMouseDown || !instance.rootElement) {
  84. return; // it must have been started in a cell
  85. }
  86. // immediate click on "holder" means click on the right side of vertical scrollbar
  87. if (next === instance.view.wt.wtTable.holder) {
  88. var scrollbarWidth = getScrollbarWidth();
  89. if (document.elementFromPoint(eventX + scrollbarWidth, eventY) !== instance.view.wt.wtTable.holder ||
  90. document.elementFromPoint(eventX, eventY + scrollbarWidth) !== instance.view.wt.wtTable.holder) {
  91. return;
  92. }
  93. } else {
  94. while (next !== document.documentElement) {
  95. if (next === null) {
  96. if (event.isTargetWebComponent) {
  97. break;
  98. }
  99. // click on something that was a row but now is detached (possibly because your click triggered a rerender)
  100. return;
  101. }
  102. if (next === instance.rootElement) {
  103. // click inside container
  104. return;
  105. }
  106. next = next.parentNode;
  107. }
  108. }
  109. // function did not return until here, we have an outside click!
  110. var outsideClickDeselects = typeof that.settings.outsideClickDeselects === 'function' ?
  111. that.settings.outsideClickDeselects(originalTarget) :
  112. that.settings.outsideClickDeselects;
  113. if (outsideClickDeselects) {
  114. instance.deselectCell();
  115. } else {
  116. instance.destroyEditor();
  117. }
  118. });
  119. this.eventManager.addEventListener(table, 'selectstart', function(event) {
  120. if (that.settings.fragmentSelection || isInput(event.target)) {
  121. return;
  122. }
  123. // https://github.com/handsontable/handsontable/issues/160
  124. // Prevent text from being selected when performing drag down.
  125. event.preventDefault();
  126. });
  127. var clearTextSelection = function() {
  128. // http://stackoverflow.com/questions/3169786/clear-text-selection-with-javascript
  129. if (window.getSelection) {
  130. if (window.getSelection().empty) { // Chrome
  131. window.getSelection().empty();
  132. } else if (window.getSelection().removeAllRanges) { // Firefox
  133. window.getSelection().removeAllRanges();
  134. }
  135. } else if (document.selection) { // IE?
  136. document.selection.empty();
  137. }
  138. };
  139. var selections = [
  140. new Selection({
  141. className: 'current',
  142. border: {
  143. width: 2,
  144. color: '#5292F7',
  145. // style: 'solid', // not used
  146. cornerVisible: function() {
  147. return that.settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple();
  148. },
  149. multipleSelectionHandlesVisible: function() {
  150. return !that.isCellEdited() && !instance.selection.isMultiple();
  151. },
  152. },
  153. }),
  154. new Selection({
  155. className: 'area',
  156. border: {
  157. width: 1,
  158. color: '#89AFF9',
  159. // style: 'solid', // not used
  160. cornerVisible: function() {
  161. return that.settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple();
  162. },
  163. multipleSelectionHandlesVisible: function() {
  164. return !that.isCellEdited() && instance.selection.isMultiple();
  165. },
  166. },
  167. }),
  168. new Selection({
  169. className: 'highlight',
  170. highlightHeaderClassName: that.settings.currentHeaderClassName,
  171. highlightRowClassName: that.settings.currentRowClassName,
  172. highlightColumnClassName: that.settings.currentColClassName,
  173. }),
  174. new Selection({
  175. className: 'fill',
  176. border: {
  177. width: 1,
  178. color: 'red',
  179. // style: 'solid' // not used
  180. },
  181. }),
  182. ];
  183. selections.current = selections[0];
  184. selections.area = selections[1];
  185. selections.highlight = selections[2];
  186. selections.fill = selections[3];
  187. var walkontableConfig = {
  188. debug: function() {
  189. return that.settings.debug;
  190. },
  191. externalRowCalculator: this.instance.getPlugin('autoRowSize') && this.instance.getPlugin('autoRowSize').isEnabled(),
  192. table: table,
  193. preventOverflow: () => this.settings.preventOverflow,
  194. stretchH: function() {
  195. return that.settings.stretchH;
  196. },
  197. data: instance.getDataAtCell,
  198. totalRows: () => instance.countRows(),
  199. totalColumns: () => instance.countCols(),
  200. fixedColumnsLeft: function() {
  201. return that.settings.fixedColumnsLeft;
  202. },
  203. fixedRowsTop: function() {
  204. return that.settings.fixedRowsTop;
  205. },
  206. fixedRowsBottom: function() {
  207. return that.settings.fixedRowsBottom;
  208. },
  209. minSpareRows: function() {
  210. return that.settings.minSpareRows;
  211. },
  212. renderAllRows: that.settings.renderAllRows,
  213. rowHeaders: function() {
  214. let headerRenderers = [];
  215. if (instance.hasRowHeaders()) {
  216. headerRenderers.push(function(row, TH) {
  217. that.appendRowHeader(row, TH);
  218. });
  219. }
  220. instance.runHooks('afterGetRowHeaderRenderers', headerRenderers);
  221. return headerRenderers;
  222. },
  223. columnHeaders: function() {
  224. let headerRenderers = [];
  225. if (instance.hasColHeaders()) {
  226. headerRenderers.push(function(column, TH) {
  227. that.appendColHeader(column, TH);
  228. });
  229. }
  230. instance.runHooks('afterGetColumnHeaderRenderers', headerRenderers);
  231. return headerRenderers;
  232. },
  233. columnWidth: instance.getColWidth,
  234. rowHeight: instance.getRowHeight,
  235. cellRenderer: function(row, col, TD) {
  236. const cellProperties = that.instance.getCellMeta(row, col);
  237. const prop = that.instance.colToProp(col);
  238. let value = that.instance.getDataAtRowProp(row, prop);
  239. if (that.instance.hasHook('beforeValueRender')) {
  240. value = that.instance.runHooks('beforeValueRender', value);
  241. }
  242. that.instance.runHooks('beforeRenderer', TD, row, col, prop, value, cellProperties);
  243. that.instance.getCellRenderer(cellProperties)(that.instance, TD, row, col, prop, value, cellProperties);
  244. that.instance.runHooks('afterRenderer', TD, row, col, prop, value, cellProperties);
  245. },
  246. selections: selections,
  247. hideBorderOnMouseDownOver: function() {
  248. return that.settings.fragmentSelection;
  249. },
  250. onCellMouseDown: function(event, coords, TD, wt) {
  251. let blockCalculations = {
  252. row: false,
  253. column: false,
  254. cells: false
  255. };
  256. instance.listen();
  257. that.activeWt = wt;
  258. isMouseDown = true;
  259. instance.runHooks('beforeOnCellMouseDown', event, coords, TD, blockCalculations);
  260. if (isImmediatePropagationStopped(event)) {
  261. return;
  262. }
  263. let actualSelection = instance.getSelectedRange();
  264. let selection = instance.selection;
  265. let selectedHeader = selection.selectedHeader;
  266. if (event.shiftKey && actualSelection) {
  267. if (coords.row >= 0 && coords.col >= 0 && !blockCalculations.cells) {
  268. selection.setSelectedHeaders(false, false);
  269. selection.setRangeEnd(coords);
  270. } else if ((selectedHeader.cols || selectedHeader.rows) && coords.row >= 0 && coords.col >= 0 && !blockCalculations.cells) {
  271. selection.setSelectedHeaders(false, false);
  272. selection.setRangeEnd(new CellCoords(coords.row, coords.col));
  273. } else if (selectedHeader.cols && coords.row < 0 && !blockCalculations.column) {
  274. selection.setRangeEnd(new CellCoords(actualSelection.to.row, coords.col));
  275. } else if (selectedHeader.rows && coords.col < 0 && !blockCalculations.row) {
  276. selection.setRangeEnd(new CellCoords(coords.row, actualSelection.to.col));
  277. } else if (((!selectedHeader.cols && !selectedHeader.rows && coords.col < 0) ||
  278. (selectedHeader.cols && coords.col < 0)) && !blockCalculations.row) {
  279. selection.setSelectedHeaders(true, false);
  280. selection.setRangeStartOnly(new CellCoords(actualSelection.from.row, 0));
  281. selection.setRangeEnd(new CellCoords(coords.row, instance.countCols() - 1));
  282. } else if (((!selectedHeader.cols && !selectedHeader.rows && coords.row < 0) ||
  283. (selectedHeader.rows && coords.row < 0)) && !blockCalculations.column) {
  284. selection.setSelectedHeaders(false, true);
  285. selection.setRangeStartOnly(new CellCoords(0, actualSelection.from.col));
  286. selection.setRangeEnd(new CellCoords(instance.countRows() - 1, coords.col));
  287. }
  288. } else {
  289. let doNewSelection = true;
  290. if (actualSelection) {
  291. let {from, to} = actualSelection;
  292. let coordsNotInSelection = !selection.inInSelection(coords);
  293. if (coords.row < 0 && selectedHeader.cols) {
  294. let start = Math.min(from.col, to.col);
  295. let end = Math.max(from.col, to.col);
  296. doNewSelection = (coords.col < start || coords.col > end);
  297. } else if (coords.col < 0 && selectedHeader.rows) {
  298. let start = Math.min(from.row, to.row);
  299. let end = Math.max(from.row, to.row);
  300. doNewSelection = (coords.row < start || coords.row > end);
  301. } else {
  302. doNewSelection = coordsNotInSelection;
  303. }
  304. }
  305. const rightClick = isRightClick(event);
  306. const leftClick = isLeftClick(event) || event.type === 'touchstart';
  307. // clicked row header and when some column was selected
  308. if (coords.row < 0 && coords.col >= 0 && !blockCalculations.column) {
  309. selection.setSelectedHeaders(false, true);
  310. if (leftClick || (rightClick && doNewSelection)) {
  311. selection.setRangeStartOnly(new CellCoords(0, coords.col));
  312. selection.setRangeEnd(new CellCoords(Math.max(instance.countRows() - 1, 0), coords.col), false);
  313. }
  314. // clicked column header and when some row was selected
  315. } else if (coords.col < 0 && coords.row >= 0 && !blockCalculations.row) {
  316. selection.setSelectedHeaders(true, false);
  317. if (leftClick || (rightClick && doNewSelection)) {
  318. selection.setRangeStartOnly(new CellCoords(coords.row, 0));
  319. selection.setRangeEnd(new CellCoords(coords.row, Math.max(instance.countCols() - 1, 0)), false);
  320. }
  321. } else if (coords.col >= 0 && coords.row >= 0 && !blockCalculations.cells) {
  322. if (leftClick || (rightClick && doNewSelection)) {
  323. selection.setSelectedHeaders(false, false);
  324. selection.setRangeStart(coords);
  325. }
  326. } else if (coords.col < 0 && coords.row < 0) {
  327. coords.row = 0;
  328. coords.col = 0;
  329. selection.setSelectedHeaders(false, false, true);
  330. selection.setRangeStart(coords);
  331. }
  332. }
  333. instance.runHooks('afterOnCellMouseDown', event, coords, TD);
  334. that.activeWt = that.wt;
  335. },
  336. onCellMouseOut: function(event, coords, TD, wt) {
  337. that.activeWt = wt;
  338. instance.runHooks('beforeOnCellMouseOut', event, coords, TD);
  339. if (isImmediatePropagationStopped(event)) {
  340. return;
  341. }
  342. instance.runHooks('afterOnCellMouseOut', event, coords, TD);
  343. that.activeWt = that.wt;
  344. },
  345. onCellMouseOver: function(event, coords, TD, wt) {
  346. let blockCalculations = {
  347. row: false,
  348. column: false,
  349. cell: false
  350. };
  351. that.activeWt = wt;
  352. instance.runHooks('beforeOnCellMouseOver', event, coords, TD, blockCalculations);
  353. if (isImmediatePropagationStopped(event)) {
  354. return;
  355. }
  356. if (event.button === 0 && isMouseDown) {
  357. if (coords.row >= 0 && coords.col >= 0) { // is not a header
  358. if (instance.selection.selectedHeader.cols && !blockCalculations.column) {
  359. instance.selection.setRangeEnd(new CellCoords(instance.countRows() - 1, coords.col), false);
  360. } else if (instance.selection.selectedHeader.rows && !blockCalculations.row) {
  361. instance.selection.setRangeEnd(new CellCoords(coords.row, instance.countCols() - 1), false);
  362. } else if (!blockCalculations.cell) {
  363. instance.selection.setRangeEnd(coords);
  364. }
  365. } else {
  366. /* eslint-disable no-lonely-if */
  367. if (instance.selection.selectedHeader.cols && !blockCalculations.column) {
  368. instance.selection.setRangeEnd(new CellCoords(instance.countRows() - 1, coords.col), false);
  369. } else if (instance.selection.selectedHeader.rows && !blockCalculations.row) {
  370. instance.selection.setRangeEnd(new CellCoords(coords.row, instance.countCols() - 1), false);
  371. } else if (!blockCalculations.cell) {
  372. instance.selection.setRangeEnd(coords);
  373. }
  374. }
  375. }
  376. instance.runHooks('afterOnCellMouseOver', event, coords, TD);
  377. that.activeWt = that.wt;
  378. },
  379. onCellMouseUp: function(event, coords, TD, wt) {
  380. that.activeWt = wt;
  381. instance.runHooks('beforeOnCellMouseUp', event, coords, TD);
  382. instance.runHooks('afterOnCellMouseUp', event, coords, TD);
  383. that.activeWt = that.wt;
  384. },
  385. onCellCornerMouseDown: function(event) {
  386. event.preventDefault();
  387. instance.runHooks('afterOnCellCornerMouseDown', event);
  388. },
  389. onCellCornerDblClick: function(event) {
  390. event.preventDefault();
  391. instance.runHooks('afterOnCellCornerDblClick', event);
  392. },
  393. beforeDraw: function(force, skipRender) {
  394. that.beforeRender(force, skipRender);
  395. },
  396. onDraw: function(force) {
  397. that.onDraw(force);
  398. },
  399. onScrollVertically: function() {
  400. instance.runHooks('afterScrollVertically');
  401. },
  402. onScrollHorizontally: function() {
  403. instance.runHooks('afterScrollHorizontally');
  404. },
  405. onBeforeDrawBorders: function(corners, borderClassName) {
  406. instance.runHooks('beforeDrawBorders', corners, borderClassName);
  407. },
  408. onBeforeTouchScroll: function() {
  409. instance.runHooks('beforeTouchScroll');
  410. },
  411. onAfterMomentumScroll: function() {
  412. instance.runHooks('afterMomentumScroll');
  413. },
  414. onBeforeStretchingColumnWidth: function(stretchedWidth, column) {
  415. return instance.runHooks('beforeStretchingColumnWidth', stretchedWidth, column);
  416. },
  417. onModifyRowHeaderWidth: function(rowHeaderWidth) {
  418. return instance.runHooks('modifyRowHeaderWidth', rowHeaderWidth);
  419. },
  420. viewportRowCalculatorOverride: function(calc) {
  421. let rows = instance.countRows();
  422. let viewportOffset = that.settings.viewportRowRenderingOffset;
  423. if (viewportOffset === 'auto' && that.settings.fixedRowsTop) {
  424. viewportOffset = 10;
  425. }
  426. if (typeof viewportOffset === 'number') {
  427. calc.startRow = Math.max(calc.startRow - viewportOffset, 0);
  428. calc.endRow = Math.min(calc.endRow + viewportOffset, rows - 1);
  429. }
  430. if (viewportOffset === 'auto') {
  431. let center = calc.startRow + calc.endRow - calc.startRow;
  432. let offset = Math.ceil(center / rows * 12);
  433. calc.startRow = Math.max(calc.startRow - offset, 0);
  434. calc.endRow = Math.min(calc.endRow + offset, rows - 1);
  435. }
  436. instance.runHooks('afterViewportRowCalculatorOverride', calc);
  437. },
  438. viewportColumnCalculatorOverride: function(calc) {
  439. let cols = instance.countCols();
  440. let viewportOffset = that.settings.viewportColumnRenderingOffset;
  441. if (viewportOffset === 'auto' && that.settings.fixedColumnsLeft) {
  442. viewportOffset = 10;
  443. }
  444. if (typeof viewportOffset === 'number') {
  445. calc.startColumn = Math.max(calc.startColumn - viewportOffset, 0);
  446. calc.endColumn = Math.min(calc.endColumn + viewportOffset, cols - 1);
  447. }
  448. if (viewportOffset === 'auto') {
  449. let center = calc.startColumn + calc.endColumn - calc.startColumn;
  450. let offset = Math.ceil(center / cols * 12);
  451. calc.startRow = Math.max(calc.startColumn - offset, 0);
  452. calc.endColumn = Math.min(calc.endColumn + offset, cols - 1);
  453. }
  454. instance.runHooks('afterViewportColumnCalculatorOverride', calc);
  455. },
  456. rowHeaderWidth: function() {
  457. return that.settings.rowHeaderWidth;
  458. },
  459. columnHeaderHeight: function() {
  460. const columnHeaderHeight = instance.runHooks('modifyColumnHeaderHeight');
  461. return that.settings.columnHeaderHeight || columnHeaderHeight;
  462. }
  463. };
  464. instance.runHooks('beforeInitWalkontable', walkontableConfig);
  465. this.wt = new Walkontable(walkontableConfig);
  466. this.activeWt = this.wt;
  467. if (!isChrome() && !isSafari()) {
  468. this.eventManager.addEventListener(instance.rootElement, 'wheel', (event) => {
  469. event.preventDefault();
  470. const lineHeight = parseInt(getComputedStyle(document.body)['font-size'], 10);
  471. const holder = that.wt.wtOverlays.scrollableElement;
  472. let deltaY = event.wheelDeltaY || event.deltaY;
  473. let deltaX = event.wheelDeltaX || event.deltaX;
  474. switch (event.deltaMode) {
  475. case 0:
  476. holder.scrollLeft += deltaX;
  477. holder.scrollTop += deltaY;
  478. break;
  479. case 1:
  480. holder.scrollLeft += deltaX * lineHeight;
  481. holder.scrollTop += deltaY * lineHeight;
  482. break;
  483. default:
  484. break;
  485. }
  486. });
  487. }
  488. this.eventManager.addEventListener(that.wt.wtTable.spreader, 'mousedown', function(event) {
  489. // right mouse button exactly on spreader means right click on the right hand side of vertical scrollbar
  490. if (event.target === that.wt.wtTable.spreader && event.which === 3) {
  491. stopPropagation(event);
  492. }
  493. });
  494. this.eventManager.addEventListener(that.wt.wtTable.spreader, 'contextmenu', function(event) {
  495. // right mouse button exactly on spreader means right click on the right hand side of vertical scrollbar
  496. if (event.target === that.wt.wtTable.spreader && event.which === 3) {
  497. stopPropagation(event);
  498. }
  499. });
  500. this.eventManager.addEventListener(document.documentElement, 'click', function() {
  501. if (that.settings.observeDOMVisibility) {
  502. if (that.wt.drawInterrupted) {
  503. that.instance.forceFullRender = true;
  504. that.render();
  505. }
  506. }
  507. });
  508. }
  509. TableView.prototype.isTextSelectionAllowed = function(el) {
  510. if (isInput(el)) {
  511. return true;
  512. }
  513. let isChildOfTableBody = isChildOf(el, this.instance.view.wt.wtTable.spreader);
  514. if (this.settings.fragmentSelection === true && isChildOfTableBody) {
  515. return true;
  516. }
  517. if (this.settings.fragmentSelection === 'cell' && this.isSelectedOnlyCell() && isChildOfTableBody) {
  518. return true;
  519. }
  520. if (!this.settings.fragmentSelection && this.isCellEdited() && this.isSelectedOnlyCell()) {
  521. return true;
  522. }
  523. return false;
  524. };
  525. /**
  526. * Check if selected only one cell.
  527. *
  528. * @returns {Boolean}
  529. */
  530. TableView.prototype.isSelectedOnlyCell = function() {
  531. var [row, col, rowEnd, colEnd] = this.instance.getSelected() || [];
  532. return row !== void 0 && row === rowEnd && col === colEnd;
  533. };
  534. TableView.prototype.isCellEdited = function() {
  535. var activeEditor = this.instance.getActiveEditor();
  536. return activeEditor && activeEditor.isOpened();
  537. };
  538. TableView.prototype.beforeRender = function(force, skipRender) {
  539. if (force) {
  540. // this.instance.forceFullRender = did Handsontable request full render?
  541. this.instance.runHooks('beforeRender', this.instance.forceFullRender, skipRender);
  542. }
  543. };
  544. TableView.prototype.onDraw = function(force) {
  545. if (force) {
  546. // this.instance.forceFullRender = did Handsontable request full render?
  547. this.instance.runHooks('afterRender', this.instance.forceFullRender);
  548. }
  549. };
  550. TableView.prototype.render = function() {
  551. this.wt.draw(!this.instance.forceFullRender);
  552. this.instance.forceFullRender = false;
  553. this.instance.renderCall = false;
  554. };
  555. /**
  556. * Returns td object given coordinates
  557. *
  558. * @param {CellCoords} coords
  559. * @param {Boolean} topmost
  560. */
  561. TableView.prototype.getCellAtCoords = function(coords, topmost) {
  562. var td = this.wt.getCell(coords, topmost);
  563. if (td < 0) { // there was an exit code (cell is out of bounds)
  564. return null;
  565. }
  566. return td;
  567. };
  568. /**
  569. * Scroll viewport to selection.
  570. *
  571. * @param {CellCoords} coords
  572. */
  573. TableView.prototype.scrollViewport = function(coords) {
  574. this.wt.scrollViewport(coords);
  575. };
  576. /**
  577. * Append row header to a TH element
  578. * @param row
  579. * @param TH
  580. */
  581. TableView.prototype.appendRowHeader = function(row, TH) {
  582. if (TH.firstChild) {
  583. let container = TH.firstChild;
  584. if (!hasClass(container, 'relative')) {
  585. empty(TH);
  586. this.appendRowHeader(row, TH);
  587. return;
  588. }
  589. this.updateCellHeader(container.querySelector('.rowHeader'), row, this.instance.getRowHeader);
  590. } else {
  591. let div = document.createElement('div');
  592. let span = document.createElement('span');
  593. div.className = 'relative';
  594. span.className = 'rowHeader';
  595. this.updateCellHeader(span, row, this.instance.getRowHeader);
  596. div.appendChild(span);
  597. TH.appendChild(div);
  598. }
  599. this.instance.runHooks('afterGetRowHeader', row, TH);
  600. };
  601. /**
  602. * Append column header to a TH element
  603. * @param col
  604. * @param TH
  605. */
  606. TableView.prototype.appendColHeader = function(col, TH) {
  607. if (TH.firstChild) {
  608. let container = TH.firstChild;
  609. if (hasClass(container, 'relative')) {
  610. this.updateCellHeader(container.querySelector('.colHeader'), col, this.instance.getColHeader);
  611. } else {
  612. empty(TH);
  613. this.appendColHeader(col, TH);
  614. }
  615. } else {
  616. var div = document.createElement('div');
  617. let span = document.createElement('span');
  618. div.className = 'relative';
  619. span.className = 'colHeader';
  620. this.updateCellHeader(span, col, this.instance.getColHeader);
  621. div.appendChild(span);
  622. TH.appendChild(div);
  623. }
  624. this.instance.runHooks('afterGetColHeader', col, TH);
  625. };
  626. /**
  627. * Update header cell content
  628. *
  629. * @since 0.15.0-beta4
  630. * @param {HTMLElement} element Element to update
  631. * @param {Number} index Row index or column index
  632. * @param {Function} content Function which should be returns content for this cell
  633. */
  634. TableView.prototype.updateCellHeader = function(element, index, content) {
  635. let renderedIndex = index;
  636. let parentOverlay = this.wt.wtOverlays.getParentOverlay(element) || this.wt;
  637. // prevent wrong calculations from SampleGenerator
  638. if (element.parentNode) {
  639. if (hasClass(element, 'colHeader')) {
  640. renderedIndex = parentOverlay.wtTable.columnFilter.sourceToRendered(index);
  641. } else if (hasClass(element, 'rowHeader')) {
  642. renderedIndex = parentOverlay.wtTable.rowFilter.sourceToRendered(index);
  643. }
  644. }
  645. if (renderedIndex > -1) {
  646. fastInnerHTML(element, content(index));
  647. } else {
  648. // workaround for https://github.com/handsontable/handsontable/issues/1946
  649. fastInnerText(element, String.fromCharCode(160));
  650. addClass(element, 'cornerHeader');
  651. }
  652. };
  653. /**
  654. * Given a element's left position relative to the viewport, returns maximum element width until the right
  655. * edge of the viewport (before scrollbar)
  656. *
  657. * @param {Number} leftOffset
  658. * @return {Number}
  659. */
  660. TableView.prototype.maximumVisibleElementWidth = function(leftOffset) {
  661. var workspaceWidth = this.wt.wtViewport.getWorkspaceWidth();
  662. var maxWidth = workspaceWidth - leftOffset;
  663. return maxWidth > 0 ? maxWidth : 0;
  664. };
  665. /**
  666. * Given a element's top position relative to the viewport, returns maximum element height until the bottom
  667. * edge of the viewport (before scrollbar)
  668. *
  669. * @param {Number} topOffset
  670. * @return {Number}
  671. */
  672. TableView.prototype.maximumVisibleElementHeight = function(topOffset) {
  673. var workspaceHeight = this.wt.wtViewport.getWorkspaceHeight();
  674. var maxHeight = workspaceHeight - topOffset;
  675. return maxHeight > 0 ? maxHeight : 0;
  676. };
  677. TableView.prototype.mainViewIsActive = function() {
  678. return this.wt === this.activeWt;
  679. };
  680. TableView.prototype.destroy = function() {
  681. this.wt.destroy();
  682. this.eventManager.destroy();
  683. };
  684. export default TableView;