| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- import {
- getScrollbarWidth,
- getScrollTop,
- getStyle,
- offset,
- outerHeight,
- outerWidth,
- } from './../../../helpers/dom/element';
- import {objectEach} from './../../../helpers/object';
- import EventManager from './../../../eventManager';
- import ViewportColumnsCalculator from './calculator/viewportColumns';
- import ViewportRowsCalculator from './calculator/viewportRows';
- /**
- * @class Viewport
- */
- class Viewport {
- /**
- * @param wotInstance
- */
- constructor(wotInstance) {
- this.wot = wotInstance;
- // legacy support
- this.instance = this.wot;
- this.oversizedRows = [];
- this.oversizedColumnHeaders = [];
- this.hasOversizedColumnHeadersMarked = {};
- this.clientHeight = 0;
- this.containerWidth = NaN;
- this.rowHeaderWidth = NaN;
- this.rowsVisibleCalculator = null;
- this.columnsVisibleCalculator = null;
- this.eventManager = new EventManager(this.wot);
- this.eventManager.addEventListener(window, 'resize', () => {
- this.clientHeight = this.getWorkspaceHeight();
- });
- }
- /**
- * @returns {number}
- */
- getWorkspaceHeight() {
- let trimmingContainer = this.instance.wtOverlays.topOverlay.trimmingContainer;
- let elemHeight;
- let height = 0;
- if (trimmingContainer === window) {
- height = document.documentElement.clientHeight;
- } else {
- elemHeight = outerHeight(trimmingContainer);
- // returns height without DIV scrollbar
- height = (elemHeight > 0 && trimmingContainer.clientHeight > 0) ? trimmingContainer.clientHeight : Infinity;
- }
- return height;
- }
- getWorkspaceWidth() {
- let width;
- let totalColumns = this.wot.getSetting('totalColumns');
- let trimmingContainer = this.instance.wtOverlays.leftOverlay.trimmingContainer;
- let overflow;
- let stretchSetting = this.wot.getSetting('stretchH');
- let docOffsetWidth = document.documentElement.offsetWidth;
- let preventOverflow = this.wot.getSetting('preventOverflow');
- if (preventOverflow) {
- return outerWidth(this.instance.wtTable.wtRootElement);
- }
- if (this.wot.getSetting('freezeOverlays')) {
- width = Math.min(docOffsetWidth - this.getWorkspaceOffset().left, docOffsetWidth);
- } else {
- width = Math.min(this.getContainerFillWidth(), docOffsetWidth - this.getWorkspaceOffset().left, docOffsetWidth);
- }
- if (trimmingContainer === window && totalColumns > 0 && this.sumColumnWidths(0, totalColumns - 1) > width) {
- // in case sum of column widths is higher than available stylesheet width, let's assume using the whole window
- // otherwise continue below, which will allow stretching
- // this is used in `scroll_window.html`
- // TODO test me
- return document.documentElement.clientWidth;
- }
- if (trimmingContainer !== window) {
- overflow = getStyle(this.instance.wtOverlays.leftOverlay.trimmingContainer, 'overflow');
- if (overflow == 'scroll' || overflow == 'hidden' || overflow == 'auto') {
- // this is used in `scroll.html`
- // TODO test me
- return Math.max(width, trimmingContainer.clientWidth);
- }
- }
- if (stretchSetting === 'none' || !stretchSetting) {
- // if no stretching is used, return the maximum used workspace width
- return Math.max(width, outerWidth(this.instance.wtTable.TABLE));
- }
- // if stretching is used, return the actual container width, so the columns can fit inside it
- return width;
- }
- /**
- * Checks if viewport has vertical scroll
- *
- * @returns {Boolean}
- */
- hasVerticalScroll() {
- return this.getWorkspaceActualHeight() > this.getWorkspaceHeight();
- }
- /**
- * Checks if viewport has horizontal scroll
- *
- * @returns {Boolean}
- */
- hasHorizontalScroll() {
- return this.getWorkspaceActualWidth() > this.getWorkspaceWidth();
- }
- /**
- * @param from
- * @param length
- * @returns {Number}
- */
- sumColumnWidths(from, length) {
- let sum = 0;
- while (from < length) {
- sum += this.wot.wtTable.getColumnWidth(from);
- from++;
- }
- return sum;
- }
- /**
- * @returns {Number}
- */
- getContainerFillWidth() {
- if (this.containerWidth) {
- return this.containerWidth;
- }
- let mainContainer = this.instance.wtTable.holder;
- let fillWidth;
- let dummyElement;
- dummyElement = document.createElement('div');
- dummyElement.style.width = '100%';
- dummyElement.style.height = '1px';
- mainContainer.appendChild(dummyElement);
- fillWidth = dummyElement.offsetWidth;
- this.containerWidth = fillWidth;
- mainContainer.removeChild(dummyElement);
- return fillWidth;
- }
- /**
- * @returns {Number}
- */
- getWorkspaceOffset() {
- return offset(this.wot.wtTable.TABLE);
- }
- /**
- * @returns {Number}
- */
- getWorkspaceActualHeight() {
- return outerHeight(this.wot.wtTable.TABLE);
- }
- /**
- * @returns {Number}
- */
- getWorkspaceActualWidth() {
- return outerWidth(this.wot.wtTable.TABLE) ||
- outerWidth(this.wot.wtTable.TBODY) ||
- outerWidth(this.wot.wtTable.THEAD); // IE8 reports 0 as <table> offsetWidth;
- }
- /**
- * @returns {Number}
- */
- getColumnHeaderHeight() {
- if (isNaN(this.columnHeaderHeight)) {
- this.columnHeaderHeight = outerHeight(this.wot.wtTable.THEAD);
- }
- return this.columnHeaderHeight;
- }
- /**
- * @returns {Number}
- */
- getViewportHeight() {
- let containerHeight = this.getWorkspaceHeight();
- let columnHeaderHeight;
- if (containerHeight === Infinity) {
- return containerHeight;
- }
- columnHeaderHeight = this.getColumnHeaderHeight();
- if (columnHeaderHeight > 0) {
- containerHeight -= columnHeaderHeight;
- }
- return containerHeight;
- }
- /**
- * @returns {Number}
- */
- getRowHeaderWidth() {
- let rowHeadersHeightSetting = this.instance.getSetting('rowHeaderWidth');
- let rowHeaders = this.instance.getSetting('rowHeaders');
- if (rowHeadersHeightSetting) {
- this.rowHeaderWidth = 0;
- for (let i = 0, len = rowHeaders.length; i < len; i++) {
- this.rowHeaderWidth += rowHeadersHeightSetting[i] || rowHeadersHeightSetting;
- }
- }
- if (this.wot.cloneSource) {
- return this.wot.cloneSource.wtViewport.getRowHeaderWidth();
- }
- if (isNaN(this.rowHeaderWidth)) {
- if (rowHeaders.length) {
- let TH = this.instance.wtTable.TABLE.querySelector('TH');
- this.rowHeaderWidth = 0;
- for (let i = 0, len = rowHeaders.length; i < len; i++) {
- if (TH) {
- this.rowHeaderWidth += outerWidth(TH);
- TH = TH.nextSibling;
- } else {
- // yes this is a cheat but it worked like that before, just taking assumption from CSS instead of measuring.
- // TODO: proper fix
- this.rowHeaderWidth += 50;
- }
- }
- } else {
- this.rowHeaderWidth = 0;
- }
- }
- this.rowHeaderWidth = this.instance.getSetting('onModifyRowHeaderWidth', this.rowHeaderWidth) || this.rowHeaderWidth;
- return this.rowHeaderWidth;
- }
- /**
- * @returns {Number}
- */
- getViewportWidth() {
- let containerWidth = this.getWorkspaceWidth();
- let rowHeaderWidth;
- if (containerWidth === Infinity) {
- return containerWidth;
- }
- rowHeaderWidth = this.getRowHeaderWidth();
- if (rowHeaderWidth > 0) {
- return containerWidth - rowHeaderWidth;
- }
- return containerWidth;
- }
- /**
- * Creates:
- * - rowsRenderCalculator (before draw, to qualify rows for rendering)
- * - rowsVisibleCalculator (after draw, to measure which rows are actually visible)
- *
- * @returns {ViewportRowsCalculator}
- */
- createRowsCalculator(visible = false) {
- let height;
- let pos;
- let fixedRowsTop;
- let scrollbarHeight;
- let fixedRowsBottom;
- let fixedRowsHeight;
- let totalRows;
- this.rowHeaderWidth = NaN;
- if (this.wot.wtSettings.settings.renderAllRows) {
- height = Infinity;
- } else {
- height = this.getViewportHeight();
- }
- pos = this.wot.wtOverlays.topOverlay.getScrollPosition() - this.wot.wtOverlays.topOverlay.getTableParentOffset();
- if (pos < 0) {
- pos = 0;
- }
- fixedRowsTop = this.wot.getSetting('fixedRowsTop');
- fixedRowsBottom = this.wot.getSetting('fixedRowsBottom');
- totalRows = this.wot.getSetting('totalRows');
- if (fixedRowsTop) {
- fixedRowsHeight = this.wot.wtOverlays.topOverlay.sumCellSizes(0, fixedRowsTop);
- pos += fixedRowsHeight;
- height -= fixedRowsHeight;
- }
- if (fixedRowsBottom && this.wot.wtOverlays.bottomOverlay.clone) {
- fixedRowsHeight = this.wot.wtOverlays.bottomOverlay.sumCellSizes(totalRows - fixedRowsBottom, totalRows);
- height -= fixedRowsHeight;
- }
- if (this.wot.wtTable.holder.clientHeight === this.wot.wtTable.holder.offsetHeight) {
- scrollbarHeight = 0;
- } else {
- scrollbarHeight = getScrollbarWidth();
- }
- return new ViewportRowsCalculator(
- height,
- pos,
- this.wot.getSetting('totalRows'),
- (sourceRow) => this.wot.wtTable.getRowHeight(sourceRow),
- visible ? null : this.wot.wtSettings.settings.viewportRowCalculatorOverride,
- visible,
- scrollbarHeight
- );
- }
- /**
- * Creates:
- * - columnsRenderCalculator (before draw, to qualify columns for rendering)
- * - columnsVisibleCalculator (after draw, to measure which columns are actually visible)
- *
- * @returns {ViewportRowsCalculator}
- */
- createColumnsCalculator(visible = false) {
- let width = this.getViewportWidth();
- let pos;
- let fixedColumnsLeft;
- this.columnHeaderHeight = NaN;
- pos = this.wot.wtOverlays.leftOverlay.getScrollPosition() - this.wot.wtOverlays.leftOverlay.getTableParentOffset();
- if (pos < 0) {
- pos = 0;
- }
- fixedColumnsLeft = this.wot.getSetting('fixedColumnsLeft');
- if (fixedColumnsLeft) {
- let fixedColumnsWidth = this.wot.wtOverlays.leftOverlay.sumCellSizes(0, fixedColumnsLeft);
- pos += fixedColumnsWidth;
- width -= fixedColumnsWidth;
- }
- if (this.wot.wtTable.holder.clientWidth !== this.wot.wtTable.holder.offsetWidth) {
- width -= getScrollbarWidth();
- }
- return new ViewportColumnsCalculator(
- width,
- pos,
- this.wot.getSetting('totalColumns'),
- (sourceCol) => this.wot.wtTable.getColumnWidth(sourceCol),
- visible ? null : this.wot.wtSettings.settings.viewportColumnCalculatorOverride,
- visible,
- this.wot.getSetting('stretchH'),
- (stretchedWidth, column) => this.wot.getSetting('onBeforeStretchingColumnWidth', stretchedWidth, column)
- );
- }
- /**
- * Creates rowsRenderCalculator and columnsRenderCalculator (before draw, to determine what rows and
- * cols should be rendered)
- *
- * @param fastDraw {Boolean} If `true`, will try to avoid full redraw and only update the border positions.
- * If `false` or `undefined`, will perform a full redraw
- * @returns fastDraw {Boolean} The fastDraw value, possibly modified
- */
- createRenderCalculators(fastDraw = false) {
- if (fastDraw) {
- let proposedRowsVisibleCalculator = this.createRowsCalculator(true);
- let proposedColumnsVisibleCalculator = this.createColumnsCalculator(true);
- if (!(this.areAllProposedVisibleRowsAlreadyRendered(proposedRowsVisibleCalculator) &&
- this.areAllProposedVisibleColumnsAlreadyRendered(proposedColumnsVisibleCalculator))) {
- fastDraw = false;
- }
- }
- if (!fastDraw) {
- this.rowsRenderCalculator = this.createRowsCalculator();
- this.columnsRenderCalculator = this.createColumnsCalculator();
- }
- // delete temporarily to make sure that renderers always use rowsRenderCalculator, not rowsVisibleCalculator
- this.rowsVisibleCalculator = null;
- this.columnsVisibleCalculator = null;
- return fastDraw;
- }
- /**
- * Creates rowsVisibleCalculator and columnsVisibleCalculator (after draw, to determine what are
- * the actually visible rows and columns)
- */
- createVisibleCalculators() {
- this.rowsVisibleCalculator = this.createRowsCalculator(true);
- this.columnsVisibleCalculator = this.createColumnsCalculator(true);
- }
- /**
- * Returns information whether proposedRowsVisibleCalculator viewport
- * is contained inside rows rendered in previous draw (cached in rowsRenderCalculator)
- *
- * @param {Object} proposedRowsVisibleCalculator
- * @returns {Boolean} Returns `true` if all proposed visible rows are already rendered (meaning: redraw is not needed).
- * Returns `false` if at least one proposed visible row is not already rendered (meaning: redraw is needed)
- */
- areAllProposedVisibleRowsAlreadyRendered(proposedRowsVisibleCalculator) {
- if (this.rowsVisibleCalculator) {
- if (proposedRowsVisibleCalculator.startRow < this.rowsRenderCalculator.startRow ||
- (proposedRowsVisibleCalculator.startRow === this.rowsRenderCalculator.startRow &&
- proposedRowsVisibleCalculator.startRow > 0)) {
- return false;
- } else if (proposedRowsVisibleCalculator.endRow > this.rowsRenderCalculator.endRow ||
- (proposedRowsVisibleCalculator.endRow === this.rowsRenderCalculator.endRow &&
- proposedRowsVisibleCalculator.endRow < this.wot.getSetting('totalRows') - 1)) {
- return false;
- }
- return true;
- }
- return false;
- }
- /**
- * Returns information whether proposedColumnsVisibleCalculator viewport
- * is contained inside column rendered in previous draw (cached in columnsRenderCalculator)
- *
- * @param {Object} proposedColumnsVisibleCalculator
- * @returns {Boolean} Returns `true` if all proposed visible columns are already rendered (meaning: redraw is not needed).
- * Returns `false` if at least one proposed visible column is not already rendered (meaning: redraw is needed)
- */
- areAllProposedVisibleColumnsAlreadyRendered(proposedColumnsVisibleCalculator) {
- if (this.columnsVisibleCalculator) {
- if (proposedColumnsVisibleCalculator.startColumn < this.columnsRenderCalculator.startColumn ||
- (proposedColumnsVisibleCalculator.startColumn === this.columnsRenderCalculator.startColumn &&
- proposedColumnsVisibleCalculator.startColumn > 0)) {
- return false;
- } else if (proposedColumnsVisibleCalculator.endColumn > this.columnsRenderCalculator.endColumn ||
- (proposedColumnsVisibleCalculator.endColumn === this.columnsRenderCalculator.endColumn &&
- proposedColumnsVisibleCalculator.endColumn < this.wot.getSetting('totalColumns') - 1)) {
- return false;
- }
- return true;
- }
- return false;
- }
- /**
- * Resets values in keys of the hasOversizedColumnHeadersMarked object after updateSettings.
- */
- resetHasOversizedColumnHeadersMarked() {
- objectEach(this.hasOversizedColumnHeadersMarked, (value, key, object) => {
- object[key] = void 0;
- });
- }
- }
- export default Viewport;
|