| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- import {
- addClass,
- empty,
- fastInnerHTML,
- fastInnerText,
- getScrollbarWidth,
- hasClass,
- isChildOf,
- isInput,
- isOutsideInput
- } from './helpers/dom/element';
- import EventManager from './eventManager';
- import { stopPropagation, isImmediatePropagationStopped, isRightClick, isLeftClick } from './helpers/dom/event';
- import Walkontable from './3rdparty/walkontable/src';
- import { handleMouseEvent } from './selection/mouseEventHandler';
- /**
- * Cross-platform helper to clear text selection.
- */
- const clearTextSelection = function() {
- // http://stackoverflow.com/questions/3169786/clear-text-selection-with-javascript
- if (window.getSelection) {
- if (window.getSelection().empty) { // Chrome
- window.getSelection().empty();
- } else if (window.getSelection().removeAllRanges) { // Firefox
- window.getSelection().removeAllRanges();
- }
- } else if (document.selection) { // IE?
- document.selection.empty();
- }
- };
- /**
- * Handsontable TableView constructor
- * @param {Object} instance
- */
- function TableView(instance) {
- const that = this;
- this.eventManager = new EventManager(instance);
- this.instance = instance;
- this.settings = instance.getSettings();
- this.selectionMouseDown = false;
- const originalStyle = instance.rootElement.getAttribute('style');
- if (originalStyle) {
- instance.rootElement.setAttribute('data-originalstyle', originalStyle); // needed to retrieve original style in jsFiddle link generator in HT examples. may be removed in future versions
- }
- addClass(instance.rootElement, 'handsontable');
- const table = document.createElement('TABLE');
- addClass(table, 'htCore');
- if (instance.getSettings().tableClassName) {
- addClass(table, instance.getSettings().tableClassName);
- }
- this.THEAD = document.createElement('THEAD');
- table.appendChild(this.THEAD);
- this.TBODY = document.createElement('TBODY');
- table.appendChild(this.TBODY);
- instance.table = table;
- instance.container.insertBefore(table, instance.container.firstChild);
- this.eventManager.addEventListener(instance.rootElement, 'mousedown', (event) => {
- this.selectionMouseDown = true;
- if (!that.isTextSelectionAllowed(event.target)) {
- clearTextSelection();
- event.preventDefault();
- window.focus(); // make sure that window that contains HOT is active. Important when HOT is in iframe.
- }
- });
- this.eventManager.addEventListener(instance.rootElement, 'mouseup', () => {
- this.selectionMouseDown = false;
- });
- this.eventManager.addEventListener(instance.rootElement, 'mousemove', (event) => {
- if (this.selectionMouseDown && !that.isTextSelectionAllowed(event.target)) {
- // Clear selection only when fragmentSelection is enabled, otherwise clearing selection breakes the IME editor.
- if (this.settings.fragmentSelection) {
- clearTextSelection();
- }
- event.preventDefault();
- }
- });
- this.eventManager.addEventListener(document.documentElement, 'keyup', (event) => {
- if (instance.selection.isInProgress() && !event.shiftKey) {
- instance.selection.finish();
- }
- });
- let isMouseDown;
- this.isMouseDown = function() {
- return isMouseDown;
- };
- this.eventManager.addEventListener(document.documentElement, 'mouseup', (event) => {
- if (instance.selection.isInProgress() && isLeftClick(event)) { // is left mouse button
- instance.selection.finish();
- }
- isMouseDown = false;
- if (isOutsideInput(document.activeElement) || (!instance.selection.isSelected() && !isRightClick(event))) {
- instance.unlisten();
- }
- });
- this.eventManager.addEventListener(document.documentElement, 'contextmenu', (event) => {
- if (instance.selection.isInProgress() && isRightClick(event)) {
- instance.selection.finish();
- isMouseDown = false;
- }
- });
- this.eventManager.addEventListener(document.documentElement, 'touchend', () => {
- if (instance.selection.isInProgress()) {
- instance.selection.finish();
- }
- isMouseDown = false;
- });
- this.eventManager.addEventListener(document.documentElement, 'mousedown', (event) => {
- const originalTarget = event.target;
- const eventX = event.x || event.clientX;
- const eventY = event.y || event.clientY;
- let next = event.target;
- if (isMouseDown || !instance.rootElement) {
- return; // it must have been started in a cell
- }
- // immediate click on "holder" means click on the right side of vertical scrollbar
- if (next === instance.view.wt.wtTable.holder) {
- const scrollbarWidth = getScrollbarWidth();
- if (document.elementFromPoint(eventX + scrollbarWidth, eventY) !== instance.view.wt.wtTable.holder ||
- document.elementFromPoint(eventX, eventY + scrollbarWidth) !== instance.view.wt.wtTable.holder) {
- return;
- }
- } else {
- while (next !== document.documentElement) {
- if (next === null) {
- if (event.isTargetWebComponent) {
- break;
- }
- // click on something that was a row but now is detached (possibly because your click triggered a rerender)
- return;
- }
- if (next === instance.rootElement) {
- // click inside container
- return;
- }
- next = next.parentNode;
- }
- }
- // function did not return until here, we have an outside click!
- const outsideClickDeselects = typeof that.settings.outsideClickDeselects === 'function' ?
- that.settings.outsideClickDeselects(originalTarget) :
- that.settings.outsideClickDeselects;
- if (outsideClickDeselects) {
- instance.deselectCell();
- } else {
- instance.destroyEditor(false, false, true);
- instance.unlisten();// 点击tab等其它handsontable外的元素时,handsontable要解除监听键盘等事件. handsontable只在mouseup事件通过isOutsideInput只针对input 等几个输入元素
- }
- });
- this.eventManager.addEventListener(table, 'selectstart', (event) => {
- if (that.settings.fragmentSelection || isInput(event.target)) {
- return;
- }
- // https://github.com/handsontable/handsontable/issues/160
- // Prevent text from being selected when performing drag down.
- event.preventDefault();
- });
- const walkontableConfig = {
- debug: () => that.settings.debug,
- externalRowCalculator: this.instance.getPlugin('autoRowSize') && this.instance.getPlugin('autoRowSize').isEnabled(),
- table,
- preventOverflow: () => this.settings.preventOverflow,
- stretchH: () => that.settings.stretchH,
- data: instance.getDataAtCell,
- totalRows: () => instance.countRows(),
- totalColumns: () => instance.countCols(),
- fixedColumnsLeft: () => that.settings.fixedColumnsLeft,
- fixedRowsTop: () => that.settings.fixedRowsTop,
- fixedRowsBottom: () => that.settings.fixedRowsBottom,
- minSpareRows: () => that.settings.minSpareRows,
- renderAllRows: that.settings.renderAllRows,
- rowHeaders: () => {
- const headerRenderers = [];
- if (instance.hasRowHeaders()) {
- headerRenderers.push((row, TH) => that.appendRowHeader(row, TH));
- }
- instance.runHooks('afterGetRowHeaderRenderers', headerRenderers);
- return headerRenderers;
- },
- columnHeaders: () => {
- const headerRenderers = [];
- if (instance.hasColHeaders()) {
- headerRenderers.push((column, TH) => {
- that.appendColHeader(column, TH);
- });
- }
- instance.runHooks('afterGetColumnHeaderRenderers', headerRenderers);
- return headerRenderers;
- },
- columnWidth: instance.getColWidth,
- rowHeight: instance.getRowHeight,
- cellRenderer(row, col, TD) {
- const cellProperties = that.instance.getCellMeta(row, col);
- const prop = that.instance.colToProp(col);
- let value = that.instance.getDataAtRowProp(row, prop);
- if (that.instance.hasHook('beforeValueRender')) {
- value = that.instance.runHooks('beforeValueRender', value, cellProperties);
- }
- that.instance.runHooks('beforeRenderer', TD, row, col, prop, value, cellProperties);
- that.instance.getCellRenderer(cellProperties)(that.instance, TD, row, col, prop, value, cellProperties);
- that.instance.runHooks('afterRenderer', TD, row, col, prop, value, cellProperties);
- },
- selections: that.instance.selection.highlight,
- hideBorderOnMouseDownOver: () => that.settings.fragmentSelection,
- onCellMouseDown: (event, coords, TD, wt) => {
- const blockCalculations = {
- row: false,
- column: false,
- cell: false
- };
- instance.listen();
- that.activeWt = wt;
- isMouseDown = true;
- instance.runHooks('beforeOnCellMouseDown', event, coords, TD, blockCalculations);
- if (isImmediatePropagationStopped(event)) {
- return;
- }
- handleMouseEvent(event, {
- coords,
- selection: instance.selection,
- controller: blockCalculations,
- });
- instance.runHooks('afterOnCellMouseDown', event, coords, TD);
- that.activeWt = that.wt;
- },
- onCellContextMenu: (event, coords, TD, wt) => {
- that.activeWt = wt;
- isMouseDown = false;
- if (instance.selection.isInProgress()) {
- instance.selection.finish();
- }
- instance.runHooks('beforeOnCellContextMenu', event, coords, TD);
- if (isImmediatePropagationStopped(event)) {
- return;
- }
- instance.runHooks('afterOnCellContextMenu', event, coords, TD);
- that.activeWt = that.wt;
- },
- onCellMouseOut: (event, coords, TD, wt) => {
- that.activeWt = wt;
- instance.runHooks('beforeOnCellMouseOut', event, coords, TD);
- if (isImmediatePropagationStopped(event)) {
- return;
- }
- instance.runHooks('afterOnCellMouseOut', event, coords, TD);
- that.activeWt = that.wt;
- },
- onCellMouseOver: (event, coords, TD, wt) => {
- const blockCalculations = {
- row: false,
- column: false,
- cell: false
- };
- that.activeWt = wt;
- instance.runHooks('beforeOnCellMouseOver', event, coords, TD, blockCalculations);
- if (isImmediatePropagationStopped(event)) {
- return;
- }
- if (isMouseDown) {
- handleMouseEvent(event, {
- coords,
- selection: instance.selection,
- controller: blockCalculations,
- });
- }
- instance.runHooks('afterOnCellMouseOver', event, coords, TD);
- that.activeWt = that.wt;
- },
- onCellMouseUp: (event, coords, TD, wt) => {
- that.activeWt = wt;
- instance.runHooks('beforeOnCellMouseUp', event, coords, TD);
- instance.runHooks('afterOnCellMouseUp', event, coords, TD);
- that.activeWt = that.wt;
- },
- onCellCornerMouseDown(event) {
- event.preventDefault();
- instance.runHooks('afterOnCellCornerMouseDown', event);
- },
- onCellCornerDblClick(event) {
- event.preventDefault();
- instance.runHooks('afterOnCellCornerDblClick', event);
- },
- beforeDraw(force, skipRender) {
- that.beforeRender(force, skipRender);
- },
- onDraw(force) {
- that.onDraw(force);
- },
- onScrollVertically() {
- instance.runHooks('afterScrollVertically');
- },
- onScrollHorizontally() {
- instance.runHooks('afterScrollHorizontally');
- },
- onBeforeRemoveCellClassNames: () => instance.runHooks('beforeRemoveCellClassNames'),
- onAfterDrawSelection: (currentRow, currentColumn, cornersOfSelection, layerLevel) => instance.runHooks('afterDrawSelection',
- currentRow, currentColumn, cornersOfSelection, layerLevel),
- onBeforeDrawBorders(corners, borderClassName) {
- instance.runHooks('beforeDrawBorders', corners, borderClassName);
- },
- onBeforeTouchScroll() {
- instance.runHooks('beforeTouchScroll');
- },
- onAfterMomentumScroll() {
- instance.runHooks('afterMomentumScroll');
- },
- onBeforeStretchingColumnWidth: (stretchedWidth, column) => instance.runHooks('beforeStretchingColumnWidth', stretchedWidth, column),
- onModifyRowHeaderWidth: rowHeaderWidth => instance.runHooks('modifyRowHeaderWidth', rowHeaderWidth),
- onModifyGetCellCoords: (row, column, topmost) => instance.runHooks('modifyGetCellCoords', row, column, topmost),
- viewportRowCalculatorOverride(calc) {
- const rows = instance.countRows();
- let viewportOffset = that.settings.viewportRowRenderingOffset;
- if (viewportOffset === 'auto' && that.settings.fixedRowsTop) {
- viewportOffset = 10;
- }
- if (typeof viewportOffset === 'number') {
- calc.startRow = Math.max(calc.startRow - viewportOffset, 0);
- calc.endRow = Math.min(calc.endRow + viewportOffset, rows - 1);
- }
- if (viewportOffset === 'auto') {
- const center = calc.startRow + calc.endRow - calc.startRow;
- const offset = Math.ceil(center / rows * 12);
- calc.startRow = Math.max(calc.startRow - offset, 0);
- calc.endRow = Math.min(calc.endRow + offset, rows - 1);
- }
- instance.runHooks('afterViewportRowCalculatorOverride', calc);
- },
- viewportColumnCalculatorOverride(calc) {
- const cols = instance.countCols();
- let viewportOffset = that.settings.viewportColumnRenderingOffset;
- if (viewportOffset === 'auto' && that.settings.fixedColumnsLeft) {
- viewportOffset = 10;
- }
- if (typeof viewportOffset === 'number') {
- calc.startColumn = Math.max(calc.startColumn - viewportOffset, 0);
- calc.endColumn = Math.min(calc.endColumn + viewportOffset, cols - 1);
- }
- if (viewportOffset === 'auto') {
- const center = calc.startColumn + calc.endColumn - calc.startColumn;
- const offset = Math.ceil(center / cols * 12);
- calc.startRow = Math.max(calc.startColumn - offset, 0);
- calc.endColumn = Math.min(calc.endColumn + offset, cols - 1);
- }
- instance.runHooks('afterViewportColumnCalculatorOverride', calc);
- },
- rowHeaderWidth: () => that.settings.rowHeaderWidth,
- columnHeaderHeight() {
- const columnHeaderHeight = instance.runHooks('modifyColumnHeaderHeight');
- return that.settings.columnHeaderHeight || columnHeaderHeight;
- }
- };
- instance.runHooks('beforeInitWalkontable', walkontableConfig);
- this.wt = new Walkontable(walkontableConfig);
- this.activeWt = this.wt;
- this.eventManager.addEventListener(that.wt.wtTable.spreader, 'mousedown', (event) => {
- // right mouse button exactly on spreader means right click on the right hand side of vertical scrollbar
- if (event.target === that.wt.wtTable.spreader && event.which === 3) {
- stopPropagation(event);
- }
- });
- this.eventManager.addEventListener(that.wt.wtTable.spreader, 'contextmenu', (event) => {
- // right mouse button exactly on spreader means right click on the right hand side of vertical scrollbar
- if (event.target === that.wt.wtTable.spreader && event.which === 3) {
- stopPropagation(event);
- }
- });
- this.eventManager.addEventListener(document.documentElement, 'click', () => {
- if (that.settings.observeDOMVisibility) {
- if (that.wt.drawInterrupted) {
- that.instance.forceFullRender = true;
- that.render();
- }
- }
- });
- }
- TableView.prototype.isTextSelectionAllowed = function(el) {
- if (isInput(el)) {
- return true;
- }
- const isChildOfTableBody = isChildOf(el, this.instance.view.wt.wtTable.spreader);
- if (this.settings.fragmentSelection === true && isChildOfTableBody) {
- return true;
- }
- if (this.settings.fragmentSelection === 'cell' && this.isSelectedOnlyCell() && isChildOfTableBody) {
- return true;
- }
- if (!this.settings.fragmentSelection && this.isCellEdited() && this.isSelectedOnlyCell()) {
- return true;
- }
- return false;
- };
- /**
- * Check if selected only one cell.
- *
- * @returns {Boolean}
- */
- TableView.prototype.isSelectedOnlyCell = function() {
- const [row, col, rowEnd, colEnd] = this.instance.getSelectedLast() || [];
- return row !== void 0 && row === rowEnd && col === colEnd;
- };
- TableView.prototype.isCellEdited = function() {
- const activeEditor = this.instance.getActiveEditor();
- return activeEditor && activeEditor.isOpened();
- };
- TableView.prototype.beforeRender = function(force, skipRender) {
- if (force) {
- // this.instance.forceFullRender = did Handsontable request full render?
- this.instance.runHooks('beforeRender', this.instance.forceFullRender, skipRender);
- }
- };
- TableView.prototype.onDraw = function(force) {
- if (force) {
- // this.instance.forceFullRender = did Handsontable request full render?
- this.instance.runHooks('afterRender', this.instance.forceFullRender);
- }
- };
- TableView.prototype.render = function() {
- this.wt.draw(!this.instance.forceFullRender);
- this.instance.forceFullRender = false;
- this.instance.renderCall = false;
- };
- /**
- * Returns td object given coordinates
- *
- * @param {CellCoords} coords
- * @param {Boolean} topmost
- */
- TableView.prototype.getCellAtCoords = function(coords, topmost) {
- const td = this.wt.getCell(coords, topmost);
- if (td < 0) { // there was an exit code (cell is out of bounds)
- return null;
- }
- return td;
- };
- /**
- * Scroll viewport to a cell.
- *
- * @param {CellCoords} coords
- * @param {Boolean} [snapToTop]
- * @param {Boolean} [snapToRight]
- * @param {Boolean} [snapToBottom]
- * @param {Boolean} [snapToLeft]
- * @returns {Boolean}
- */
- TableView.prototype.scrollViewport = function(coords, snapToTop, snapToRight, snapToBottom, snapToLeft) {
- return this.wt.scrollViewport(coords, snapToTop, snapToRight, snapToBottom, snapToLeft);
- };
- /**
- * Scroll viewport to a column.
- *
- * @param {Number} column Visual column index.
- * @param {Boolean} [snapToLeft]
- * @param {Boolean} [snapToRight]
- * @returns {Boolean}
- */
- TableView.prototype.scrollViewportHorizontally = function(column, snapToRight, snapToLeft) {
- return this.wt.scrollViewportHorizontally(column, snapToRight, snapToLeft);
- };
- /**
- * Scroll viewport to a row.
- *
- * @param {Number} row Visual row index.
- * @param {Boolean} [snapToTop]
- * @param {Boolean} [snapToBottom]
- * @returns {Boolean}
- */
- TableView.prototype.scrollViewportVertically = function(row, snapToTop, snapToBottom) {
- return this.wt.scrollViewportVertically(row, snapToTop, snapToBottom);
- };
- /**
- * Append row header to a TH element
- * @param row
- * @param TH
- */
- TableView.prototype.appendRowHeader = function(row, TH) {
- if (TH.firstChild) {
- const container = TH.firstChild;
- if (!hasClass(container, 'relative')) {
- empty(TH);
- this.appendRowHeader(row, TH);
- return;
- }
- this.updateCellHeader(container.querySelector('.rowHeader'), row, this.instance.getRowHeader);
- } else {
- const div = document.createElement('div');
- const span = document.createElement('span');
- div.className = 'relative';
- span.className = 'rowHeader';
- this.updateCellHeader(span, row, this.instance.getRowHeader);
- div.appendChild(span);
- TH.appendChild(div);
- }
- this.instance.runHooks('afterGetRowHeader', row, TH);
- };
- /**
- * Append column header to a TH element
- * @param col
- * @param TH
- */
- TableView.prototype.appendColHeader = function(col, TH) {
- if (TH.firstChild) {
- const container = TH.firstChild;
- if (hasClass(container, 'relative')) {
- this.updateCellHeader(container.querySelector('.colHeader'), col, this.instance.getColHeader);
- } else {
- empty(TH);
- this.appendColHeader(col, TH);
- }
- } else {
- const div = document.createElement('div');
- const span = document.createElement('span');
- div.className = 'relative';
- span.className = 'colHeader';
- this.updateCellHeader(span, col, this.instance.getColHeader);
- div.appendChild(span);
- TH.appendChild(div);
- }
- this.instance.runHooks('afterGetColHeader', col, TH);
- };
- /**
- * Update header cell content
- *
- * @since 0.15.0-beta4
- * @param {HTMLElement} element Element to update
- * @param {Number} index Row index or column index
- * @param {Function} content Function which should be returns content for this cell
- */
- TableView.prototype.updateCellHeader = function(element, index, content) {
- let renderedIndex = index;
- const parentOverlay = this.wt.wtOverlays.getParentOverlay(element) || this.wt;
- // prevent wrong calculations from SampleGenerator
- if (element.parentNode) {
- if (hasClass(element, 'colHeader')) {
- renderedIndex = parentOverlay.wtTable.columnFilter.sourceToRendered(index);
- } else if (hasClass(element, 'rowHeader')) {
- renderedIndex = parentOverlay.wtTable.rowFilter.sourceToRendered(index);
- }
- }
- if (renderedIndex > -1) {
- fastInnerHTML(element, content(index));
- } else {
- // workaround for https://github.com/handsontable/handsontable/issues/1946
- fastInnerText(element, String.fromCharCode(160));
- addClass(element, 'cornerHeader');
- }
- };
- /**
- * Given a element's left position relative to the viewport, returns maximum element width until the right
- * edge of the viewport (before scrollbar)
- *
- * @param {Number} leftOffset
- * @return {Number}
- */
- TableView.prototype.maximumVisibleElementWidth = function(leftOffset) {
- const workspaceWidth = this.wt.wtViewport.getWorkspaceWidth();
- const maxWidth = workspaceWidth - leftOffset;
- return maxWidth > 0 ? maxWidth : 0;
- };
- /**
- * Given a element's top position relative to the viewport, returns maximum element height until the bottom
- * edge of the viewport (before scrollbar)
- *
- * @param {Number} topOffset
- * @return {Number}
- */
- TableView.prototype.maximumVisibleElementHeight = function(topOffset) {
- const workspaceHeight = this.wt.wtViewport.getWorkspaceHeight();
- const maxHeight = workspaceHeight - topOffset;
- return maxHeight > 0 ? maxHeight : 0;
- };
- TableView.prototype.mainViewIsActive = function() {
- return this.wt === this.activeWt;
- };
- TableView.prototype.destroy = function() {
- this.wt.destroy();
- this.eventManager.destroy();
- };
- export default TableView;
|