UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

715 lines (714 loc) • 30.5 kB
/** * DevExtreme (esm/ui/grid_core/ui.grid_core.grid_view.js) * Version: 22.1.9 * Build date: Tue Apr 18 2023 * * Copyright (c) 2012 - 2023 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import { getOuterWidth, getInnerWidth, getWidth, getHeight } from "../../core/utils/size"; import $ from "../../core/renderer"; import modules from "./ui.grid_core.modules"; import { deferRender, deferUpdate } from "../../core/utils/common"; import { hasWindow, getWindow } from "../../core/utils/window"; import { each } from "../../core/utils/iterator"; import { isString, isDefined, isNumeric } from "../../core/utils/type"; import { getBoundingRect } from "../../core/utils/position"; import gridCoreUtils from "./ui.grid_core.utils"; import messageLocalization from "../../localization/message"; import { when, Deferred } from "../../core/utils/deferred"; import domAdapter from "../../core/dom_adapter"; import * as accessibility from "../shared/accessibility"; import browser from "../../core/utils/browser"; var BORDERS_CLASS = "borders"; var TABLE_FIXED_CLASS = "table-fixed"; var IMPORTANT_MARGIN_CLASS = "important-margin"; var GRIDBASE_CONTAINER_CLASS = "dx-gridbase-container"; var GROUP_ROW_SELECTOR = "tr.dx-group-row"; var HIDDEN_COLUMNS_WIDTH = "adaptiveHidden"; var VIEW_NAMES = ["columnsSeparatorView", "blockSeparatorView", "trackerView", "headerPanel", "columnHeadersView", "rowsView", "footerView", "columnChooserView", "filterPanelView", "pagerView", "draggingHeaderView", "contextMenuView", "errorView", "headerFilterView", "filterBuilderView"]; var isPercentWidth = function(width) { return isString(width) && "%" === width.slice(-1) }; var isPixelWidth = function(width) { return isString(width) && "px" === width.slice(-2) }; var calculateFreeWidth = function(that, widths) { var contentWidth = that._rowsView.contentWidth(); var totalWidth = that._getTotalWidth(widths, contentWidth); return contentWidth - totalWidth }; var calculateFreeWidthWithCurrentMinWidth = function(that, columnIndex, currentMinWidth, widths) { return calculateFreeWidth(that, widths.map((function(width, index) { return index === columnIndex ? currentMinWidth : width }))) }; var restoreFocus = function(focusedElement, selectionRange) { accessibility.hiddenFocus(focusedElement); gridCoreUtils.setSelectionRange(focusedElement, selectionRange) }; var ResizingController = modules.ViewController.inherit({ _initPostRenderHandlers: function() { var dataController = this._dataController; if (!this._refreshSizesHandler) { this._refreshSizesHandler = e => { dataController.changed.remove(this._refreshSizesHandler); this._refreshSizes(e) }; dataController.changed.add(() => { dataController.changed.add(this._refreshSizesHandler) }) } }, _refreshSizes: function(e) { var resizeDeferred; var that = this; var changeType = e && e.changeType; var isDelayed = e && e.isDelayed; var items = that._dataController.items(); if (!e || "refresh" === changeType || "prepend" === changeType || "append" === changeType) { if (!isDelayed) { resizeDeferred = that.resize() } } else if ("update" === changeType) { var _e$changeTypes; if (0 === (null === (_e$changeTypes = e.changeTypes) || void 0 === _e$changeTypes ? void 0 : _e$changeTypes.length)) { return } if ((items.length > 1 || "insert" !== e.changeTypes[0]) && !(0 === items.length && "remove" === e.changeTypes[0]) && !e.needUpdateDimensions) { resizeDeferred = new Deferred; this._waitAsyncTemplates().done(() => { deferUpdate(() => deferRender(() => deferUpdate(() => { that._setScrollerSpacing(); that._rowsView.resize(); resizeDeferred.resolve() }))) }).fail(resizeDeferred.reject) } else { resizeDeferred = that.resize() } } if (changeType && "updateSelection" !== changeType && "updateFocusedRow" !== changeType && "pageIndex" !== changeType && !isDelayed) { when(resizeDeferred).done((function() { that._setAriaRowColCount(); that.fireContentReadyAction() })) } }, fireContentReadyAction: function() { this.component._fireContentReadyAction() }, _setAriaRowColCount: function() { var component = this.component; component.setAria({ rowCount: this._dataController.totalItemsCount(), colCount: component.columnCount() }, component.$element().children("." + GRIDBASE_CONTAINER_CLASS)) }, _getBestFitWidths: function() { var _widths; var rowsView = this._rowsView; var columnHeadersView = this._columnHeadersView; var widths = rowsView.getColumnWidths(); if (!(null !== (_widths = widths) && void 0 !== _widths && _widths.length)) { var _rowsView$getTableEle; var headersTableElement = columnHeadersView.getTableElement(); columnHeadersView.setTableElement(null === (_rowsView$getTableEle = rowsView.getTableElement()) || void 0 === _rowsView$getTableEle ? void 0 : _rowsView$getTableEle.children(".dx-header")); widths = columnHeadersView.getColumnWidths(); columnHeadersView.setTableElement(headersTableElement) } return widths }, _setVisibleWidths: function(visibleColumns, widths) { var columnsController = this._columnsController; columnsController.beginUpdate(); each(visibleColumns, (function(index, column) { var columnId = columnsController.getColumnId(column); columnsController.columnOption(columnId, "visibleWidth", widths[index]) })); columnsController.endUpdate() }, _toggleBestFitModeForView: function(view, className, isBestFit) { if (!view || !view.isVisible()) { return } var $rowsTables = this._rowsView.getTableElements(); var $viewTables = view.getTableElements(); each($rowsTables, (index, tableElement) => { var $tableBody; var $rowsTable = $(tableElement); var $viewTable = $viewTables.eq(index); if ($viewTable && $viewTable.length) { if (isBestFit) { $tableBody = $viewTable.children("tbody").appendTo($rowsTable) } else { $tableBody = $rowsTable.children("." + className).appendTo($viewTable) } $tableBody.toggleClass(className, isBestFit); $tableBody.toggleClass(this.addWidgetPrefix("best-fit"), isBestFit) } }) }, _toggleBestFitMode: function(isBestFit) { var $rowsTable = this._rowsView.getTableElement(); var $rowsFixedTable = this._rowsView.getTableElements().eq(1); if (!$rowsTable) { return } $rowsTable.css("tableLayout", isBestFit ? "auto" : "fixed"); $rowsTable.children("colgroup").css("display", isBestFit ? "none" : ""); each($rowsFixedTable.find(GROUP_ROW_SELECTOR), (idx, item) => { $(item).css("display", isBestFit ? "none" : "") }); $rowsFixedTable.toggleClass(this.addWidgetPrefix(TABLE_FIXED_CLASS), !isBestFit); this._toggleBestFitModeForView(this._columnHeadersView, "dx-header", isBestFit); this._toggleBestFitModeForView(this._footerView, "dx-footer", isBestFit); if (this._needStretch()) { $rowsTable.get(0).style.width = isBestFit ? "auto" : "" } }, _toggleContentMinHeight: function(value) { var scrollable = this._rowsView.getScrollable(); var $contentElement = this._rowsView._findContentElement(); if (false === (null === scrollable || void 0 === scrollable ? void 0 : scrollable.option("useNative"))) { $contentElement.css({ minHeight: value ? gridCoreUtils.getContentHeightLimit(browser) : "" }) } }, _synchronizeColumns: function() { var columnsController = this._columnsController; var visibleColumns = columnsController.getVisibleColumns(); var columnAutoWidth = this.option("columnAutoWidth"); var wordWrapEnabled = this.option("wordWrapEnabled"); var needBestFit = this._needBestFit(); var hasMinWidth = false; var resetBestFitMode; var isColumnWidthsCorrected = false; var resultWidths = []; var focusedElement; var selectionRange; !needBestFit && each(visibleColumns, (function(index, column) { if ("auto" === column.width) { needBestFit = true; return false } })); each(visibleColumns, (function(index, column) { if (column.minWidth) { hasMinWidth = true; return false } })); this._setVisibleWidths(visibleColumns, []); if (needBestFit) { focusedElement = domAdapter.getActiveElement(); selectionRange = gridCoreUtils.getSelectionRange(focusedElement); this._toggleBestFitMode(true); resetBestFitMode = true } this._toggleContentMinHeight(wordWrapEnabled); var $element = this.component.$element(); if ($element && $element[0] && this._maxWidth) { delete this._maxWidth; $element[0].style.maxWidth = "" } deferUpdate(() => { if (needBestFit) { resultWidths = this._getBestFitWidths(); each(visibleColumns, (function(index, column) { var columnId = columnsController.getColumnId(column); columnsController.columnOption(columnId, "bestFitWidth", resultWidths[index], true) })) } else if (hasMinWidth) { resultWidths = this._getBestFitWidths() } each(visibleColumns, (function(index) { var width = this.width; if ("auto" !== width) { if (isDefined(width)) { resultWidths[index] = isNumeric(width) || isPixelWidth(width) ? parseFloat(width) : width } else if (!columnAutoWidth) { resultWidths[index] = void 0 } } })); if (resetBestFitMode) { this._toggleBestFitMode(false); resetBestFitMode = false; if (focusedElement && focusedElement !== domAdapter.getActiveElement()) { var isFocusOutsideWindow = getBoundingRect(focusedElement).bottom < 0; if (!isFocusOutsideWindow) { restoreFocus(focusedElement, selectionRange) } } } isColumnWidthsCorrected = this._correctColumnWidths(resultWidths, visibleColumns); if (columnAutoWidth) { ! function() { var expandColumnWidth; each(visibleColumns, (function(index, column) { if ("groupExpand" === column.type) { expandColumnWidth = resultWidths[index] } })); each(visibleColumns, (function(index, column) { if ("groupExpand" === column.type && expandColumnWidth) { resultWidths[index] = expandColumnWidth } })) }(); if (this._needStretch()) { this._processStretch(resultWidths, visibleColumns) } } deferRender(() => { if (needBestFit || isColumnWidthsCorrected) { this._setVisibleWidths(visibleColumns, resultWidths) } if (wordWrapEnabled) { this._toggleContentMinHeight(false) } }) }) }, _needBestFit: function() { return this.option("columnAutoWidth") }, _needStretch: function() { return this._columnsController.getVisibleColumns().some(c => "auto" === c.width && !c.command) }, _getAverageColumnsWidth: function(resultWidths) { var freeWidth = calculateFreeWidth(this, resultWidths); var columnCountWithoutWidth = resultWidths.filter((function(width) { return void 0 === width })).length; return freeWidth / columnCountWithoutWidth }, _correctColumnWidths: function(resultWidths, visibleColumns) { var that = this; var i; var hasPercentWidth = false; var hasAutoWidth = false; var isColumnWidthsCorrected = false; var $element = that.component.$element(); var hasWidth = that._hasWidth; var _loop = function() { var index = i; var column = visibleColumns[index]; var isHiddenColumn = resultWidths[index] === HIDDEN_COLUMNS_WIDTH; var width = resultWidths[index]; var minWidth = column.minWidth; if (minWidth) { if (void 0 === width) { var averageColumnsWidth = that._getAverageColumnsWidth(resultWidths); width = averageColumnsWidth } else if (isPercentWidth(width)) { var freeWidth = calculateFreeWidthWithCurrentMinWidth(that, index, minWidth, resultWidths); if (freeWidth < 0) { width = -1 } } } var realColumnWidth = that._getRealColumnWidth(index, resultWidths.map((function(columnWidth, columnIndex) { return index === columnIndex ? width : columnWidth }))); if (minWidth && !isHiddenColumn && realColumnWidth < minWidth) { resultWidths[index] = minWidth; isColumnWidthsCorrected = true; i = -1 } if (!isDefined(column.width)) { hasAutoWidth = true } if (isPercentWidth(column.width)) { hasPercentWidth = true } }; for (i = 0; i < visibleColumns.length; i++) { _loop() } if (!hasAutoWidth && resultWidths.length) { var $rowsViewElement = that._rowsView.element(); var contentWidth = that._rowsView.contentWidth(); var scrollbarWidth = that._rowsView.getScrollbarWidth(); var totalWidth = that._getTotalWidth(resultWidths, contentWidth); if (totalWidth < contentWidth) { var lastColumnIndex = gridCoreUtils.getLastResizableColumnIndex(visibleColumns, resultWidths); if (lastColumnIndex >= 0) { resultWidths[lastColumnIndex] = "auto"; isColumnWidthsCorrected = true; if (false === hasWidth && !hasPercentWidth) { var borderWidth = that.option("showBorders") ? Math.ceil(getOuterWidth($rowsViewElement) - getInnerWidth($rowsViewElement)) : 0; that._maxWidth = totalWidth + scrollbarWidth + borderWidth; $element.css("maxWidth", that._maxWidth) } } } } return isColumnWidthsCorrected }, _processStretch: function(resultSizes, visibleColumns) { var groupSize = this._rowsView.contentWidth(); var tableSize = this._getTotalWidth(resultSizes, groupSize); var unusedIndexes = { length: 0 }; if (!resultSizes.length) { return } each(visibleColumns, (function(index) { if (this.width || resultSizes[index] === HIDDEN_COLUMNS_WIDTH) { unusedIndexes[index] = true; unusedIndexes.length++ } })); var diff = groupSize - tableSize; var diffElement = Math.floor(diff / (resultSizes.length - unusedIndexes.length)); var onePixelElementsCount = diff - diffElement * (resultSizes.length - unusedIndexes.length); if (diff >= 0) { for (var i = 0; i < resultSizes.length; i++) { if (unusedIndexes[i]) { continue } resultSizes[i] += diffElement; if (onePixelElementsCount > 0) { if (onePixelElementsCount < 1) { resultSizes[i] += onePixelElementsCount; onePixelElementsCount = 0 } else { resultSizes[i]++; onePixelElementsCount-- } } } } }, _getRealColumnWidth: function(columnIndex, columnWidths, groupWidth) { var ratio = 1; var width = columnWidths[columnIndex]; if (!isPercentWidth(width)) { return parseFloat(width) } var percentTotalWidth = columnWidths.reduce((sum, width, index) => { if (!isPercentWidth(width)) { return sum } return sum + parseFloat(width) }, 0); var pixelTotalWidth = columnWidths.reduce((sum, width) => { if (!width || width === HIDDEN_COLUMNS_WIDTH || isPercentWidth(width)) { return sum } return sum + parseFloat(width) }, 0); groupWidth = groupWidth || this._rowsView.contentWidth(); var freeSpace = groupWidth - pixelTotalWidth; var percentTotalWidthInPixel = percentTotalWidth * groupWidth / 100; if (pixelTotalWidth > 0 && percentTotalWidthInPixel + pixelTotalWidth >= groupWidth) { ratio = percentTotalWidthInPixel > freeSpace ? freeSpace / percentTotalWidthInPixel : 1 } return parseFloat(width) * groupWidth * ratio / 100 }, _getTotalWidth: function(widths, groupWidth) { var result = 0; for (var i = 0; i < widths.length; i++) { var width = widths[i]; if (width && width !== HIDDEN_COLUMNS_WIDTH) { result += this._getRealColumnWidth(i, widths, groupWidth) } } return Math.ceil(result) }, _getGroupElement: function() { return this.component.$element().children().get(0) }, updateSize: function(rootElement) { var $rootElement = $(rootElement); var importantMarginClass = this.addWidgetPrefix(IMPORTANT_MARGIN_CLASS); if (void 0 === this._hasHeight && $rootElement && $rootElement.is(":visible") && getWidth($rootElement)) { var $groupElement = $rootElement.children("." + this.getWidgetContainerClass()); if ($groupElement.length) { $groupElement.detach() } this._hasHeight = !!getHeight($rootElement); var width = getWidth($rootElement); $rootElement.addClass(importantMarginClass); this._hasWidth = getWidth($rootElement) === width; $rootElement.removeClass(importantMarginClass); if ($groupElement.length) { $groupElement.appendTo($rootElement) } } }, publicMethods: function() { return ["resize", "updateDimensions"] }, _waitAsyncTemplates: function() { var _this$_columnHeadersV, _this$_rowsView, _this$_footerView; return when(null === (_this$_columnHeadersV = this._columnHeadersView) || void 0 === _this$_columnHeadersV ? void 0 : _this$_columnHeadersV.waitAsyncTemplates(), null === (_this$_rowsView = this._rowsView) || void 0 === _this$_rowsView ? void 0 : _this$_rowsView.waitAsyncTemplates(), null === (_this$_footerView = this._footerView) || void 0 === _this$_footerView ? void 0 : _this$_footerView.waitAsyncTemplates()) }, resize: function() { if (this.component._requireResize) { return } var d = new Deferred; this._waitAsyncTemplates().done(() => { when(this.updateDimensions()).done(d.resolve).fail(d.reject) }).fail(d.reject); return d.promise() }, updateDimensions: function(checkSize) { var that = this; that._initPostRenderHandlers(); if (!that._checkSize(checkSize)) { return } var prevResult = that._resizeDeferred; var result = that._resizeDeferred = new Deferred; when(prevResult).always((function() { deferRender((function() { if (that._dataController.isLoaded()) { that._synchronizeColumns() } that._resetGroupElementHeight(); deferUpdate((function() { deferRender((function() { deferUpdate((function() { that._updateDimensionsCore() })) })) })) })).done(result.resolve).fail(result.reject) })); return result.promise() }, _resetGroupElementHeight: function() { var groupElement = this._getGroupElement(); var scrollable = this._rowsView.getScrollable(); if (groupElement && groupElement.style.height && (!scrollable || !scrollable.scrollTop())) { groupElement.style.height = "" } }, _checkSize: function(checkSize) { var $rootElement = this.component.$element(); if (checkSize && (this._lastWidth === getWidth($rootElement) && this._lastHeight === getHeight($rootElement) && this._devicePixelRatio === getWindow().devicePixelRatio || !$rootElement.is(":visible"))) { return false } return true }, _setScrollerSpacingCore: function() { var that = this; var vScrollbarWidth = that._rowsView.getScrollbarWidth(); var hScrollbarWidth = that._rowsView.getScrollbarWidth(true); deferRender((function() { that._columnHeadersView && that._columnHeadersView.setScrollerSpacing(vScrollbarWidth); that._footerView && that._footerView.setScrollerSpacing(vScrollbarWidth); that._rowsView.setScrollerSpacing(vScrollbarWidth, hScrollbarWidth) })) }, _setScrollerSpacing: function() { var scrollable = this._rowsView.getScrollable(); var isNativeScrolling = true === this.option("scrolling.useNative"); if (!scrollable || isNativeScrolling) { deferRender(() => { deferUpdate(() => { this._setScrollerSpacingCore() }) }) } else { this._setScrollerSpacingCore() } }, _updateDimensionsCore: function() { var that = this; var dataController = that._dataController; var editorFactory = that.getController("editorFactory"); var rowsView = that._rowsView; var $rootElement = that.component.$element(); var groupElement = this._getGroupElement(); var rootElementHeight = getHeight($rootElement); var height = that.option("height") || $rootElement.get(0).style.height; var isHeightSpecified = !!height && "auto" !== height; var maxHeight = parseInt($rootElement.css("maxHeight")); var maxHeightHappened = maxHeight && rootElementHeight >= maxHeight; var isMaxHeightApplied = groupElement && groupElement.scrollHeight === groupElement.offsetHeight; that.updateSize($rootElement); deferRender((function() { var hasHeight = that._hasHeight || !!maxHeight || isHeightSpecified; rowsView.hasHeight(hasHeight); if (maxHeightHappened && !isMaxHeightApplied) { $(groupElement).css("height", maxHeight) } if (!dataController.isLoaded()) { rowsView.setLoading(dataController.isLoading()); return } deferUpdate((function() { that._updateLastSizes($rootElement); that._setScrollerSpacing(); each(VIEW_NAMES, (function(index, viewName) { var view = that.getView(viewName); if (view) { view.resize() } })); editorFactory && editorFactory.resize() })) })) }, _updateLastSizes: function($rootElement) { this._lastWidth = getWidth($rootElement); this._lastHeight = getHeight($rootElement); this._devicePixelRatio = getWindow().devicePixelRatio }, optionChanged: function(args) { switch (args.name) { case "width": case "height": this.component._renderDimensions(); this.resize(); case "renderAsync": args.handled = true; return; default: this.callBase(args) } }, init: function() { this._dataController = this.getController("data"); this._columnsController = this.getController("columns"); this._columnHeadersView = this.getView("columnHeadersView"); this._footerView = this.getView("footerView"); this._rowsView = this.getView("rowsView") } }); var SynchronizeScrollingController = modules.ViewController.inherit({ _scrollChangedHandler: function(views, pos, viewName) { for (var j = 0; j < views.length; j++) { if (views[j] && views[j].name !== viewName) { views[j].scrollTo({ left: pos.left, top: pos.top }) } } }, init: function() { var views = [this.getView("columnHeadersView"), this.getView("footerView"), this.getView("rowsView")]; for (var i = 0; i < views.length; i++) { var view = views[i]; if (view) { view.scrollChanged.add(this._scrollChangedHandler.bind(this, views)) } } } }); var GridView = modules.View.inherit({ _endUpdateCore: function() { if (this.component._requireResize) { this.component._requireResize = false; this._resizingController.resize() } }, _getWidgetAriaLabel: function() { return "dxDataGrid-ariaDataGrid" }, init: function() { this._resizingController = this.getController("resizing"); this._dataController = this.getController("data") }, getView: function(name) { return this.component._views[name] }, element: function() { return this._groupElement }, optionChanged: function(args) { if (isDefined(this._groupElement) && "showBorders" === args.name) { this._groupElement.toggleClass(this.addWidgetPrefix(BORDERS_CLASS), !!args.value); args.handled = true } else { this.callBase(args) } }, _renderViews: function($groupElement) { var that = this; each(VIEW_NAMES, (function(index, viewName) { var view = that.getView(viewName); if (view) { view.render($groupElement) } })) }, _getTableRoleName: function() { return "grid" }, render: function($rootElement) { var isFirstRender = !this._groupElement; var $groupElement = this._groupElement || $("<div>").addClass(this.getWidgetContainerClass()); $groupElement.addClass(GRIDBASE_CONTAINER_CLASS); $groupElement.toggleClass(this.addWidgetPrefix(BORDERS_CLASS), !!this.option("showBorders")); this.setAria("role", "presentation", $rootElement); this.component.setAria({ role: this._getTableRoleName(), label: messageLocalization.format(this._getWidgetAriaLabel()) }, $groupElement); this._rootElement = $rootElement || this._rootElement; if (isFirstRender) { this._groupElement = $groupElement; hasWindow() && this.getController("resizing").updateSize($rootElement); $groupElement.appendTo($rootElement) } this._renderViews($groupElement) }, update: function() { var $rootElement = this._rootElement; var $groupElement = this._groupElement; var resizingController = this.getController("resizing"); if ($rootElement && $groupElement) { resizingController.resize(); if (this._dataController.isLoaded()) { this._resizingController.fireContentReadyAction() } } } }); export var gridViewModule = { defaultOptions: function() { return { showBorders: false, renderAsync: void 0 } }, controllers: { resizing: ResizingController, synchronizeScrolling: SynchronizeScrollingController }, views: { gridView: GridView }, VIEW_NAMES: VIEW_NAMES };