devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
1,210 lines (1,204 loc) • 63.1 kB
JavaScript
/**
* DevExtreme (esm/__internal/grids/pivot_grid/m_widget.js)
* Version: 26.1.4
* Build date: Fri Jul 24 2026
*
* Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
name as clickEventName
} from "../../../common/core/events/click";
import eventsEngine from "../../../common/core/events/core/events_engine";
import {
addNamespace
} from "../../../common/core/events/utils/index";
import localizationMessage from "../../../common/core/localization/message";
import registerComponent from "../../../core/component_registrator";
import {
getPublicElement
} from "../../../core/element";
import $ from "../../../core/renderer";
import {
deferRender,
deferUpdate,
noop
} from "../../../core/utils/common";
import {
Deferred,
when
} from "../../../core/utils/deferred";
import {
extend
} from "../../../core/utils/extend";
import {
each
} from "../../../core/utils/iterator";
import {
getHeight,
getOuterHeight,
getWidth,
setHeight
} from "../../../core/utils/size";
import {
format as formatString
} from "../../../core/utils/string";
import {
isDefined
} from "../../../core/utils/type";
import {
getWindow,
hasWindow
} from "../../../core/utils/window";
import Button from "../../../ui/button";
import ContextMenu from "../../../ui/context_menu";
import Popup from "../../../ui/popup/ui.popup";
import {
restoreFocus,
saveFocusedElementInfo
} from "../../../ui/shared/accessibility";
import {
current,
isGeneric
} from "../../../ui/themes";
import Widget from "../../core/widget/widget";
import gridCoreUtils from "../../grids/grid_core/m_utils";
import {
ChartIntegrationMixin
} from "./chart_integration/m_chart_integration";
import DataAreaImport from "./data_area/m_data_area";
import DataControllerImport from "./data_controller/m_data_controller";
import {
ExportController
} from "./export/m_export";
import {
FieldChooser
} from "./field_chooser/m_field_chooser";
import {
FieldChooserBase
} from "./field_chooser/m_field_chooser_base";
import {
FieldsArea
} from "./fields_area/m_fields_area";
import HeadersArea from "./headers_area/m_headers_area";
import {
FAKE_TABLE_CLASS,
HORIZONTAL_HEADERS_AREA_CLASS,
VERTICAL_HEADERS_AREA_CLASS
} from "./keyboard_navigation/const";
import {
RovingTabIndex
} from "./keyboard_navigation/roving_tab_index";
import {
getAdjacentCell
} from "./keyboard_navigation/table_cell_navigation";
import {
findField,
mergeArraysByMaxValue,
setFieldProperty
} from "./m_widget_utils";
const window = getWindow();
const DATA_AREA_CELL_CLASS = "dx-area-data-cell";
const ROW_AREA_CELL_CLASS = "dx-area-row-cell";
const COLUMN_AREA_CELL_CLASS = "dx-area-column-cell";
const DESCRIPTION_AREA_CELL_CLASS = "dx-area-description-cell";
const BORDERS_CLASS = "dx-pivotgrid-border";
const PIVOTGRID_CLASS = "dx-pivotgrid";
const ROW_LINES_CLASS = "dx-row-lines";
const BOTTOM_ROW_CLASS = "dx-bottom-row";
const BOTTOM_BORDER_CLASS = "dx-bottom-border";
const FIELDS_CONTAINER_CLASS = "dx-pivotgrid-fields-container";
const FIELDS_CLASS = "dx-area-fields";
const FIELD_CHOOSER_POPUP_CLASS = "dx-fieldchooser-popup";
const INCOMPRESSIBLE_FIELDS_CLASS = "dx-incompressible-fields";
const OVERFLOW_HIDDEN_CLASS = "dx-overflow-hidden";
const TR = "<tr>";
const TD = "<td>";
const DIV = "<div>";
const TEST_HEIGHT = 66666;
const FIELD_CALCULATED_OPTIONS = ["allowSorting", "allowSortingBySummary", "allowFiltering", "allowExpandAll"];
const ARROW_KEY_DIRECTIONS = {
ArrowUp: "up",
ArrowDown: "down",
ArrowLeft: "left",
ArrowRight: "right"
};
function getArraySum(array) {
let sum = 0;
each(array, (_, value) => {
sum += value || 0
});
return sum
}
function adjustSizeArray(sizeArray, space) {
const delta = space / sizeArray.length;
for (let i = 0; i < sizeArray.length; i += 1) {
sizeArray[i] -= delta
}
}
function unsubscribeScrollEvents(area) {
area.off("scroll").off("stop")
}
function subscribeToScrollEvent(area, handler) {
unsubscribeScrollEvents(area);
area.on("scroll", handler).on("stop", handler)
}
function getCommonBorderWidth(elements, direction) {
const borderStyleNames = "width" === direction ? ["borderLeftWidth", "borderRightWidth"] : ["borderTopWidth", "borderBottomWidth"];
let width = 0;
each(elements, (_, elem) => {
const computedStyle = window.getComputedStyle(elem.get(0));
borderStyleNames.forEach(borderStyleName => {
width += parseFloat(computedStyle[borderStyleName]) || 0
})
});
return width
}
function clickedOnFieldsArea($targetElement) {
return $targetElement.closest(`.${FIELDS_CLASS}`).length || $targetElement.find(`.${FIELDS_CLASS}`).length
}
class PivotGrid extends Widget {
_getDefaultOptions() {
return extend(super._getDefaultOptions(), {
scrolling: {
timeout: 300,
renderingThreshold: 150,
minTimeout: 10,
mode: "standard",
useNative: "auto",
removeInvisiblePages: true,
virtualRowHeight: 50,
virtualColumnWidth: 100,
loadTwoPagesOnStart: true
},
encodeHtml: true,
dataSource: null,
activeStateEnabled: false,
fieldChooser: {
minWidth: 250,
minHeight: 250,
enabled: true,
allowSearch: false,
searchTimeout: 500,
layout: 0,
title: localizationMessage.format("dxPivotGrid-fieldChooserTitle"),
width: 600,
height: 600,
applyChangesMode: "instantly"
},
onContextMenuPreparing: void 0,
allowSorting: false,
allowSortingBySummary: false,
allowFiltering: false,
allowExpandAll: false,
wordWrapEnabled: true,
fieldPanel: {
showColumnFields: true,
showFilterFields: true,
showDataFields: true,
showRowFields: true,
allowFieldDragging: true,
visible: false,
texts: {
columnFieldArea: localizationMessage.format("dxPivotGrid-columnFieldArea"),
rowFieldArea: localizationMessage.format("dxPivotGrid-rowFieldArea"),
filterFieldArea: localizationMessage.format("dxPivotGrid-filterFieldArea"),
dataFieldArea: localizationMessage.format("dxPivotGrid-dataFieldArea")
}
},
dataFieldArea: "column",
export: {
enabled: false,
fileName: "PivotGrid"
},
showRowTotals: true,
showRowGrandTotals: true,
showColumnTotals: true,
showColumnGrandTotals: true,
hideEmptySummaryCells: true,
showTotalsPrior: "none",
rowHeaderLayout: "standard",
loadPanel: {
enabled: true,
text: localizationMessage.format("Loading"),
width: 200,
height: 70,
showIndicator: true,
showPane: true
},
texts: {
grandTotal: localizationMessage.format("dxPivotGrid-grandTotal"),
total: localizationMessage.getFormatter("dxPivotGrid-total"),
noData: localizationMessage.format("dxDataGrid-noDataText"),
showFieldChooser: localizationMessage.format("dxPivotGrid-showFieldChooser"),
expandAll: localizationMessage.format("dxPivotGrid-expandAll"),
collapseAll: localizationMessage.format("dxPivotGrid-collapseAll"),
sortColumnBySummary: localizationMessage.getFormatter("dxPivotGrid-sortColumnBySummary"),
sortRowBySummary: localizationMessage.getFormatter("dxPivotGrid-sortRowBySummary"),
removeAllSorting: localizationMessage.format("dxPivotGrid-removeAllSorting"),
exportToExcel: localizationMessage.format("dxDataGrid-exportToExcel"),
dataNotAvailable: localizationMessage.format("dxPivotGrid-dataNotAvailable")
},
onCellClick: void 0,
onCellPrepared: void 0,
showBorders: false,
stateStoring: {
enabled: false,
storageKey: void 0,
type: "localStorage",
customLoad: null,
customSave: null,
savingTimeout: 2e3
},
onExpandValueChanging: null,
renderCellCountLimit: 2e4,
onExporting: void 0,
headerFilter: {
width: 252,
height: 325,
allowSelectAll: true,
showRelevantValues: false,
search: {
enabled: false,
timeout: 500,
editorOptions: {},
mode: "contains"
},
texts: {
emptyValue: localizationMessage.format("dxDataGrid-headerFilterEmptyValue"),
ok: localizationMessage.format("dxDataGrid-headerFilterOK"),
cancel: localizationMessage.format("dxDataGrid-headerFilterCancel")
}
}
})
}
_setDeprecatedOptions() {
super._setDeprecatedOptions();
this._deprecatedOptions = Object.assign({}, this._deprecatedOptions, {
"loadPanel.indicatorSrc": {
since: "25.2",
alias: "loadPanel.indicatorOptions.src"
}
})
}
_updateCalculatedOptions(fields) {
const that = this;
each(fields, (_, field) => {
each(FIELD_CALCULATED_OPTIONS, (_, optionName) => {
const isCalculated = field._initProperties && optionName in field._initProperties && void 0 === field._initProperties[optionName];
const needUpdate = void 0 === field[optionName] || isCalculated;
if (needUpdate) {
setFieldProperty(field, optionName, that.option(optionName))
}
})
})
}
_getDataControllerOptions() {
const that = this;
return {
component: that,
dataSource: that.option("dataSource"),
texts: that.option("texts"),
showRowTotals: that.option("showRowTotals"),
showRowGrandTotals: that.option("showRowGrandTotals"),
showColumnTotals: that.option("showColumnTotals"),
showTotalsPrior: that.option("showTotalsPrior"),
showColumnGrandTotals: that.option("showColumnGrandTotals"),
dataFieldArea: that.option("dataFieldArea"),
rowHeaderLayout: that.option("rowHeaderLayout"),
hideEmptySummaryCells: that.option("hideEmptySummaryCells"),
onFieldsPrepared(fields) {
that._updateCalculatedOptions(fields)
}
}
}
_initDataController() {
const that = this;
that._dataController && that._dataController.dispose();
that._dataController = new DataControllerImport.DataController(that._getDataControllerOptions());
if (hasWindow()) {
that._dataController.changed.add(() => {
that._render()
})
}
that._dataController.scrollChanged.add(options => {
that._scrollLeft = options.left;
that._scrollTop = options.top
});
that._dataController.loadingChanged.add(() => {
that._updateLoading()
});
that._dataController.progressChanged.add(that._updateLoading.bind(that));
that._dataController.dataSourceChanged.add(() => {
that._trigger("onChanged", void 0)
});
const expandValueChanging = that.option("onExpandValueChanging");
if (expandValueChanging) {
that._dataController.expandValueChanging.add(e => {
expandValueChanging(e)
})
}
}
_init() {
super._init();
this._initDataController();
gridCoreUtils.logHeaderFilterDeprecatedWarningIfNeed(this);
this._scrollLeft = this._scrollTop = null;
this._initActions()
}
_initActions() {
this._actions = {
onChanged: this._createActionByOption("onChanged"),
onContextMenuPreparing: this._createActionByOption("onContextMenuPreparing"),
onCellClick: this._createActionByOption("onCellClick"),
onExporting: this._createActionByOption("onExporting"),
onCellPrepared: this._createActionByOption("onCellPrepared")
}
}
_trigger(eventName, eventArg) {
this._actions[eventName](eventArg)
}
_optionChanged(args) {
const that = this;
if (FIELD_CALCULATED_OPTIONS.includes(args.name)) {
const fields = this.getDataSource().fields();
this._updateCalculatedOptions(fields)
}
switch (args.name) {
case "dataSource":
case "allowSorting":
case "allowFiltering":
case "allowExpandAll":
case "allowSortingBySummary":
case "scrolling":
case "stateStoring":
that._initDataController();
that.getFieldChooserPopup().hide();
that._renderFieldChooser();
that._invalidate();
break;
case "texts":
case "showTotalsPrior":
case "showRowTotals":
case "showRowGrandTotals":
case "showColumnTotals":
case "showColumnGrandTotals":
case "hideEmptySummaryCells":
case "dataFieldArea":
that._dataController.updateViewOptions(that._getDataControllerOptions());
break;
case "useNativeScrolling":
case "encodeHtml":
case "renderCellCountLimit":
case "onExpandValueChanging":
break;
case "rtlEnabled":
super._optionChanged(args);
that._renderFieldChooser();
that._renderContextMenu();
hasWindow() && that._renderLoadPanel(that._dataArea.groupElement(), that.$element());
that._invalidate();
break;
case "export":
that._renderDescriptionArea();
break;
case "onCellClick":
case "onContextMenuPreparing":
case "onExporting":
case "onExported":
case "onFileSaving":
case "onCellPrepared":
that._actions[args.name] = that._createActionByOption(args.name);
break;
case "fieldChooser":
that._renderFieldChooser();
that._renderDescriptionArea();
break;
case "loadPanel":
if (hasWindow()) {
if ("loadPanel.enabled" === args.fullName) {
clearTimeout(this._hideLoadingTimeoutID);
that._renderLoadPanel(that._dataArea.groupElement(), that.$element())
} else {
that._renderLoadPanel(that._dataArea.groupElement(), that.$element());
that._invalidate()
}
}
break;
case "fieldPanel":
that._renderDescriptionArea();
that._invalidate();
break;
case "headerFilter":
that._renderFieldChooser();
that._invalidate();
break;
case "showBorders":
that._tableElement().toggleClass(BORDERS_CLASS, !!args.value);
that.updateDimensions();
break;
case "wordWrapEnabled":
that._tableElement().toggleClass("dx-word-wrap", !!args.value);
that.updateDimensions();
break;
case "rowHeaderLayout":
that._tableElement().find(".dx-area-row-cell").toggleClass("dx-area-tree-view", "tree" === args.value);
that._dataController.updateViewOptions(that._getDataControllerOptions());
break;
case "height":
case "width":
that._hasHeight = null;
super._optionChanged(args);
that.resize();
break;
default:
super._optionChanged(args)
}
}
_updateScrollPosition(columnsArea, rowsArea, dataArea) {
let force = arguments.length > 3 && void 0 !== arguments[3] ? arguments[3] : false;
const that = this;
let scrollTop;
let scrollLeft;
const scrolled = that._scrollTop || that._scrollLeft;
if (that._scrollUpdating) {
return
}
that._scrollUpdating = true;
if (rowsArea && !rowsArea.hasScroll() && that._hasHeight) {
that._scrollTop = null
}
if (columnsArea && !columnsArea.hasScroll()) {
that._scrollLeft = null
}
if (null !== that._scrollTop || null !== that._scrollLeft || scrolled || that.option("rtlEnabled")) {
scrollTop = that._scrollTop || 0;
scrollLeft = that._scrollLeft || 0;
dataArea.scrollTo({
left: scrollLeft,
top: scrollTop
}, force);
columnsArea.scrollTo({
left: scrollLeft
}, force);
rowsArea.scrollTo({
top: scrollTop
}, force);
that._dataController.updateWindowScrollPosition(that._scrollTop)
}
that._scrollUpdating = false
}
_subscribeToEvents(columnsArea, rowsArea, dataArea) {
const that = this;
each([columnsArea, rowsArea, dataArea], (_, area) => {
subscribeToScrollEvent(area, e => function(e, area) {
const {
scrollOffset: scrollOffset
} = e;
const scrollable = area._getScrollable();
const leftOffset = "vertical" !== scrollable.option("direction") ? scrollOffset.left : that._scrollLeft;
const topOffset = "horizontal" !== scrollable.option("direction") && that._hasHeight ? scrollOffset.top : that._scrollTop;
if ((that._scrollLeft || 0) !== (leftOffset || 0) || (that._scrollTop || 0) !== (topOffset || 0)) {
that._scrollLeft = leftOffset;
that._scrollTop = topOffset;
that._updateScrollPosition(columnsArea, rowsArea, dataArea);
if ("virtual" === that.option("scrolling.mode")) {
that._dataController.setViewportPosition(that._scrollLeft, that._scrollTop)
}
}
}(e, area))
});
!that._hasHeight && that._dataController.subscribeToWindowScrollEvents(dataArea.groupElement())
}
_clean() {
noop()
}
_needDelayResizing(cellsInfo) {
const cellsCount = cellsInfo.length * (cellsInfo.length ? cellsInfo[0].length : 0);
return cellsCount > Number(this.option("renderCellCountLimit"))
}
_renderFieldChooser() {
const that = this;
const container = that._pivotGridContainer;
const fieldChooserOptions = that.option("fieldChooser") || {};
const toolbarItems = "onDemand" === fieldChooserOptions.applyChangesMode ? [{
toolbar: "bottom",
location: "after",
widget: "dxButton",
options: {
text: localizationMessage.format("OK"),
onClick() {
that.getFieldChooserPopup().$content().dxPivotGridFieldChooser("applyChanges");
that.getFieldChooserPopup().hide()
}
}
}, {
toolbar: "bottom",
location: "after",
widget: "dxButton",
options: {
text: localizationMessage.format("Cancel"),
onClick() {
that.getFieldChooserPopup().hide()
}
}
}] : [];
const fieldChooserComponentOptions = {
layout: fieldChooserOptions.layout,
texts: fieldChooserOptions.texts || {},
dataSource: that.getDataSource(),
allowSearch: fieldChooserOptions.allowSearch,
searchTimeout: fieldChooserOptions.searchTimeout,
width: void 0,
height: void 0,
headerFilter: that.option("headerFilter"),
encodeHtml: that.option("fieldChooser.encodeHtml") ?? that.option("encodeHtml"),
applyChangesMode: fieldChooserOptions.applyChangesMode,
rtlEnabled: that.option("rtlEnabled"),
onContextMenuPreparing(e) {
that._trigger("onContextMenuPreparing", e)
}
};
const popupOptions = {
shading: false,
title: fieldChooserOptions.title,
width: fieldChooserOptions.width,
height: fieldChooserOptions.height,
showCloseButton: true,
resizeEnabled: true,
minWidth: fieldChooserOptions.minWidth,
minHeight: fieldChooserOptions.minHeight,
toolbarItems: toolbarItems,
onResize(e) {
e.component.$content().dxPivotGridFieldChooser("updateDimensions")
},
onShown(e) {
that._createComponent(e.component.content(), FieldChooser, fieldChooserComponentOptions)
},
onHidden(e) {
const fieldChooser = e.component.$content().dxPivotGridFieldChooser("instance");
fieldChooser.resetTreeView();
fieldChooser.cancelChanges();
if (that.$fieldChooserButton) {
that.$fieldChooserButton.get(0).focus()
}
}
};
if (that.getFieldChooserPopup()) {
that._fieldChooserPopup.option(popupOptions);
that._fieldChooserPopup.$content().dxPivotGridFieldChooser(fieldChooserComponentOptions)
} else {
that._fieldChooserPopup = that._createComponent($(DIV).addClass("dx-fieldchooser-popup").appendTo(container), Popup, popupOptions)
}
}
_renderContextMenu() {
const that = this;
const $container = that._pivotGridContainer;
if (that._contextMenu) {
that._contextMenu.$element().remove()
}
that._contextMenu = that._createComponent($(DIV).appendTo($container), ContextMenu, {
onPositioning(actionArgs) {
const {
event: event
} = actionArgs;
actionArgs.cancel = true;
if (!event) {
return
}
const targetElement = event.target.cellIndex >= 0 ? event.target : $(event.target).closest("td").get(0);
if (!targetElement) {
return
}
const args = that._createEventArgs(targetElement, event);
const items = that._getContextMenuItems(args);
if (items) {
actionArgs.component.option("items", items);
if ("keydown" === event.type) {
if (actionArgs.position) {
actionArgs.position.of = $(targetElement)
}
} else {
that._contextMenuOwnerNavigation = void 0
}
actionArgs.cancel = false
}
},
onHidden() {
var _that$_contextMenuOwn;
null === (_that$_contextMenuOwn = that._contextMenuOwnerNavigation) || void 0 === _that$_contextMenuOwn || _that$_contextMenuOwn.refocusFocusedItem();
that._contextMenuOwnerNavigation = void 0
},
onItemClick(params) {
params.itemData.onItemClick && params.itemData.onItemClick(params)
},
cssClass: "dx-pivotgrid",
target: that.$element()
})
}
_getContextMenuItems(e) {
const that = this;
let items = [];
const texts = that.option("texts");
if ("row" === e.area || "column" === e.area) {
const areaFields = e[`${e.area}Fields`];
const oppositeAreaFields = e["column" === e.area ? "rowFields" : "columnFields"];
const field = e.cell.path && areaFields[e.cell.path.length - 1];
const dataSource = that.getDataSource();
if (field && field.allowExpandAll && e.cell.path.length < e[`${e.area}Fields`].length && !dataSource.paginate()) {
items.push({
beginGroup: true,
icon: "none",
text: texts.expandAll,
onItemClick() {
dataSource.expandAll(field.index)
}
});
items.push({
text: texts.collapseAll,
icon: "none",
onItemClick() {
dataSource.collapseAll(field.index)
}
})
}
if (e.cell.isLast && !dataSource.paginate()) {
let sortingBySummaryItemCount = 0;
each(oppositeAreaFields, (_, field) => {
if (!field.allowSortingBySummary) {
return
}
each(e.dataFields, (dataIndex, dataField) => {
if (isDefined(e.cell.dataIndex) && e.cell.dataIndex !== dataIndex) {
return
}
const showDataFieldCaption = !isDefined(e.cell.dataIndex) && e.dataFields.length > 1;
const textFormat = "column" === e.area ? texts.sortColumnBySummary : texts.sortRowBySummary;
const checked = findField(e.dataFields, field.sortBySummaryField) === dataIndex && (e.cell.path || []).join("/") === (field.sortBySummaryPath || []).join("/");
const text = formatString(textFormat, showDataFieldCaption ? `${field.caption} - ${dataField.caption}` : field.caption);
items.push({
beginGroup: 0 === sortingBySummaryItemCount,
icon: checked ? "desc" === field.sortOrder ? "sortdowntext" : "sortuptext" : "none",
text: text,
onItemClick() {
dataSource.field(field.index, {
sortBySummaryField: dataField.name || dataField.caption || dataField.dataField,
sortBySummaryPath: e.cell.path,
sortOrder: "desc" === field.sortOrder ? "asc" : "desc"
});
dataSource.load()
}
});
sortingBySummaryItemCount += 1
})
});
each(oppositeAreaFields, (_, field) => {
if (!field.allowSortingBySummary || !isDefined(field.sortBySummaryField)) {
return
}
items.push({
beginGroup: 0 === sortingBySummaryItemCount,
icon: "none",
text: texts.removeAllSorting,
onItemClick() {
each(oppositeAreaFields, (_, field) => {
dataSource.field(field.index, {
sortBySummaryField: void 0,
sortBySummaryPath: void 0,
sortOrder: void 0
})
});
dataSource.load()
}
});
return false
})
}
}
if (that.option("fieldChooser.enabled")) {
items.push({
beginGroup: true,
icon: "columnchooser",
text: texts.showFieldChooser,
onItemClick() {
that.getFieldChooserPopup().show()
}
})
}
if (that.option("export.enabled")) {
items.push({
beginGroup: true,
icon: "xlsxfile",
text: texts.exportToExcel,
onItemClick() {
that.exportTo()
}
})
}
e.items = items;
that._trigger("onContextMenuPreparing", e);
items = e.items;
if (items && items.length) {
return items
}
return
}
_createEventArgs(targetElement, dxEvent) {
const that = this;
const dataSource = that.getDataSource();
const args = {
rowFields: dataSource.getAreaFields("row"),
columnFields: dataSource.getAreaFields("column"),
dataFields: dataSource.getAreaFields("data"),
event: dxEvent
};
if (clickedOnFieldsArea($(targetElement))) {
return extend(that._createFieldArgs(targetElement), args)
}
return extend(that._createCellArgs(targetElement), args)
}
_createFieldArgs(targetElement) {
const field = $(targetElement).children().data("field");
const args = {
field: field
};
return isDefined(field) ? args : {}
}
_createCellArgs(cellElement) {
const $cellElement = $(cellElement);
const columnIndex = cellElement.cellIndex;
const {
rowIndex: rowIndex
} = cellElement.parentElement;
const $table = $cellElement.closest("table");
const data = $table.data("data");
const cell = data && data[rowIndex] && data[rowIndex][columnIndex];
const args = {
area: $table.data("area"),
rowIndex: rowIndex,
columnIndex: columnIndex,
cellElement: getPublicElement($cellElement),
cell: cell
};
return args
}
_handleCellClick(e) {
const that = this;
const args = that._createEventArgs(e.currentTarget, e);
const {
cell: cell
} = args;
if (!cell || !args.area && (args.rowIndex || args.columnIndex)) {
return
}
that._trigger("onCellClick", args);
cell && !args.cancel && isDefined(cell.expanded) && setTimeout(() => {
that._dataController[cell.expanded ? "collapseHeaderItem" : "expandHeaderItem"](args.area, cell.path)
})
}
_getColumnsAreaNavigation() {
this._columnsAreaNavigation = this._columnsAreaNavigation ?? new RovingTabIndex({
component: this,
getItems: () => this._getColumnHeaderCells(),
scrollToItem: item => this._columnsArea.scrollToElement(item),
getItemId: item => this._getHeaderCellId(item)
});
return this._columnsAreaNavigation
}
_getHeaderCellId(cell) {
const columnIndex = cell.getAttribute("aria-colindex");
const row = cell.parentElement;
if (!columnIndex || !row) {
return
}
return `${row.sectionRowIndex}:${columnIndex}`
}
_getRowsAreaNavigation() {
this._rowsAreaNavigation = this._rowsAreaNavigation ?? new RovingTabIndex({
component: this,
getItems: () => this._getRowHeaderCells(),
scrollToItem: item => this._rowsArea.scrollToElement(item),
getItemId: item => this._getRowHeaderCellId(item)
});
return this._rowsAreaNavigation
}
_getRowHeaderCellId(cell) {
const rowIndex = cell.getAttribute("aria-rowindex");
const {
cellIndex: cellIndex
} = cell;
if (!rowIndex || cellIndex < 0) {
return
}
return `${cellIndex}:${rowIndex}`
}
_getHeaderAreaCells(cellSelector) {
const element = this.$element().get(0);
if (!element) {
return []
}
const cells = Array.from(element.querySelectorAll(cellSelector));
return cells.filter(cell => !cell.closest(`.${FAKE_TABLE_CLASS}`))
}
_getColumnHeaderCells() {
return this._getHeaderAreaCells(`thead.${HORIZONTAL_HEADERS_AREA_CLASS} td[tabindex]`)
}
_getRowHeaderCells() {
return this._getHeaderAreaCells(`tbody.${VERTICAL_HEADERS_AREA_CLASS} td[tabindex]`)
}
_getCellAreaNavigation(cell) {
var _cell$closest, _cell$closest2, _cell$closest3;
if (null !== (_cell$closest = cell.closest) && void 0 !== _cell$closest && _cell$closest.call(cell, `.${FAKE_TABLE_CLASS}`)) {
return
}
if (null !== (_cell$closest2 = cell.closest) && void 0 !== _cell$closest2 && _cell$closest2.call(cell, `thead.${HORIZONTAL_HEADERS_AREA_CLASS}`)) {
return this._getColumnsAreaNavigation()
}
if (null !== (_cell$closest3 = cell.closest) && void 0 !== _cell$closest3 && _cell$closest3.call(cell, `tbody.${VERTICAL_HEADERS_AREA_CLASS}`)) {
return this._getRowsAreaNavigation()
}
return
}
_normalizeCellNavigationDirection(direction) {
if (!this.option("rtlEnabled")) {
return direction
}
if ("left" === direction) {
return "right"
}
if ("right" === direction) {
return "left"
}
return direction
}
_handleCellArrowKeyDown(e, direction) {
const cell = e.currentTarget;
const navigation = this._getCellAreaNavigation(cell);
if (!navigation) {
return
}
e.preventDefault();
const section = cell.closest("thead, tbody");
const normalizedDirection = this._normalizeCellNavigationDirection(direction);
let target = getAdjacentCell(section, cell, normalizedDirection);
while (target && !target.hasAttribute("tabindex")) {
target = getAdjacentCell(section, target, normalizedDirection)
}
if (target) {
navigation.focusItem(target)
}
}
_handleCellFocusIn(e) {
var _this$_getCellAreaNav;
null === (_this$_getCellAreaNav = this._getCellAreaNavigation(e.currentTarget)) || void 0 === _this$_getCellAreaNav || _this$_getCellAreaNav.handleFocusIn(e.currentTarget)
}
_handleCellContextMenuKeyDown(e) {
const cell = e.currentTarget;
const navigation = this._getCellAreaNavigation(cell);
if (!navigation || !this._contextMenu) {
return
}
this._contextMenu._show(e);
if (this._contextMenu.option("visible")) {
this._contextMenuOwnerNavigation = navigation
}
}
_handleCellKeyDown(e) {
const direction = ARROW_KEY_DIRECTIONS[e.key];
if (direction) {
this._handleCellArrowKeyDown(e, direction);
return
}
if (e.repeat) {
return
}
if ("ContextMenu" === e.key || e.shiftKey && "F10" === e.key) {
this._handleCellContextMenuKeyDown(e);
return
}
if ("Enter" !== e.key && " " !== e.key) {
return
}
const args = this._createEventArgs(e.currentTarget, e);
const {
cell: cell
} = args;
if (!cell || !isDefined(cell.expanded)) {
return
}
e.preventDefault();
this._trigger("onCellClick", args);
if (args.cancel) {
return
}
const $control = $(e.currentTarget).find(".dx-expand-icon-container");
saveFocusedElementInfo($control.get(0), this);
const onReady = () => {
this.off("contentReady", onReady);
restoreFocus(this)
};
this.on("contentReady", onReady);
setTimeout(() => {
this._dataController[cell.expanded ? "collapseHeaderItem" : "expandHeaderItem"](args.area, cell.path)
})
}
_getNoDataText() {
return this.option("texts.noData")
}
_renderNoDataText(element) {
return gridCoreUtils.renderNoDataText.call(this, element)
}
_renderLoadPanel(element, container) {
return gridCoreUtils.renderLoadPanel.call(this, element, container, void 0)
}
_updateLoading(progress) {
const that = this;
const isLoading = that._dataController.isLoading();
if (!that._loadPanel) {
return
}
const loadPanelVisible = that._loadPanel.option("visible");
if (!loadPanelVisible) {
that._startLoadingTime = new Date
}
if (isLoading) {
if (progress) {
if (new Date - that._startLoadingTime >= 1e3) {
that._loadPanel.option("message", `${Math.floor(100*progress)}%`)
}
} else {
that._loadPanel.option("message", that.option("loadPanel.text"))
}
}
clearTimeout(that._hideLoadingTimeoutID);
if (loadPanelVisible && !isLoading) {
that._hideLoadingTimeoutID = setTimeout(() => {
that._loadPanel.option("visible", false);
that.$element().removeClass("dx-overflow-hidden")
})
} else {
const visibilityOptions = {
visible: isLoading
};
if (isLoading) {
visibilityOptions.position = gridCoreUtils.calculateLoadPanelPosition(that._dataArea.groupElement())
}
that._loadPanel.option(visibilityOptions);
that.$element().toggleClass("dx-overflow-hidden", !isLoading)
}
}
_renderDescriptionArea() {
const $element = this.$element();
const $descriptionCell = $element.find(".dx-area-description-cell");
const $toolbarContainer = $(DIV).addClass("dx-pivotgrid-toolbar");
const fieldPanel = this.option("fieldPanel");
const $filterHeader = $element.find(".dx-filter-header");
const $columnHeader = $element.find(".dx-column-header");
let $targetContainer;
let buttonStylingMode = isGeneric(current()) ? "contained" : "text";
if (fieldPanel.visible && fieldPanel.showFilterFields) {
$targetContainer = $filterHeader
} else if (fieldPanel.visible && (fieldPanel.showDataFields || fieldPanel.showColumnFields)) {
$targetContainer = $columnHeader
} else {
$targetContainer = $descriptionCell;
buttonStylingMode = "text"
}
$columnHeader.toggleClass("dx-bottom-border", !!(fieldPanel.visible && (fieldPanel.showDataFields || fieldPanel.showColumnFields)));
$filterHeader.toggleClass("dx-bottom-border", !!(fieldPanel.visible && fieldPanel.showFilterFields));
$descriptionCell.toggleClass("dx-pivotgrid-background", fieldPanel.visible && (fieldPanel.showDataFields || fieldPanel.showColumnFields || fieldPanel.showRowFields));
this.$element().find(".dx-pivotgrid-toolbar").remove();
$toolbarContainer.prependTo($targetContainer);
if (this.option("fieldChooser.enabled")) {
this.$fieldChooserButton = $(DIV).appendTo($toolbarContainer).addClass("dx-pivotgrid-field-chooser-button");
const buttonOptions = {
icon: "columnchooser",
hint: this.option("texts.showFieldChooser"),
elementAttr: {
"aria-label": this.option("texts.showFieldChooser"),
"aria-haspopup": "dialog"
},
stylingMode: buttonStylingMode,
onClick: () => {
this.getFieldChooserPopup().show()
}
};
this._createComponent(this.$fieldChooserButton, Button, buttonOptions)
}
if (this.option("export.enabled")) {
const $buttonElement = $(DIV).appendTo($toolbarContainer).addClass("dx-pivotgrid-export-button");
const buttonOptions = {
icon: "xlsxfile",
hint: this.option("texts.exportToExcel"),
stylingMode: buttonStylingMode,
onClick: () => {
this.exportTo()
}
};
this._createComponent($buttonElement, Button, buttonOptions)
}
}
_detectHasContainerHeight() {
const that = this;
const element = that.$element();
if (isDefined(that._hasHeight)) {
const height = that.option("height") || that.$element().get(0).style.height;
if (height && that._hasHeight ^ "auto" !== height) {
that._hasHeight = null
}
}
if (isDefined(that._hasHeight) || element.is(":hidden")) {
return
}
that._pivotGridContainer.addClass("dx-hidden");
const testElement = $(DIV);
setHeight(testElement, 66666);
element.append(testElement);
that._hasHeight = 66666 !== getHeight(element);
that._pivotGridContainer.removeClass("dx-hidden");
testElement.remove()
}
_renderHeaders(rowHeaderContainer, columnHeaderContainer, filterHeaderContainer, dataHeaderContainer) {
const dataSource = this.getDataSource();
this._rowFields = this._rowFields || new FieldsArea(this, "row");
this._rowFields.render(rowHeaderContainer, dataSource.getAreaFields("row"));
this._columnFields = this._columnFields || new FieldsArea(this, "column");
this._columnFields.render(columnHeaderContainer, dataSource.getAreaFields("column"));
this._filterFields = this._filterFields || new FieldsArea(this, "filter");
this._filterFields.render(filterHeaderContainer, dataSource.getAreaFields("filter"));
this._dataFields = this._dataFields || new FieldsArea(this, "data");
this._dataFields.render(dataHeaderContainer, dataSource.getAreaFields("data"));
const fieldChooser = this.$element().dxPivotGridFieldChooserBase("instance");
fieldChooser.renderSortable();
fieldChooser.restoreFieldFocus()
}
_createTableElement() {
const $table = $("<table>").attr("role", "presentation").css({
width: "100%"
}).toggleClass(BORDERS_CLASS, !!this.option("showBorders")).toggleClass("dx-word-wrap", !!this.option("wordWrapEnabled"));
eventsEngine.on($table, addNamespace(clickEventName, "dxPivotGrid"), "td", this._handleCellClick.bind(this));
eventsEngine.on($table, addNamespace("keydown", "dxPivotGrid"), "td", this._handleCellKeyDown.bind(this));
eventsEngine.on($table, addNamespace("focusin", "dxPivotGrid"), "td", this._handleCellFocusIn.bind(this));
return $table
}
_renderDataArea(dataAreaElement) {
const dataArea = this._dataArea || new DataAreaImport.DataArea(this);
this._dataArea = dataArea;
dataArea.render(dataAreaElement, this._dataController.getCellsInfo());
return dataArea
}
_renderRowsArea(rowsAreaElement) {
const that = this;
const rowsArea = that._rowsArea || new HeadersArea.VerticalHeadersArea(that);
that._rowsArea = rowsArea;
const navigation = that._getRowsAreaNavigation();
const needRestoreFocus = navigation.containsActiveElement();
rowsArea.render(rowsAreaElement, that._dataController.getRowsInfo());
navigation.updateTabIndexes();
if (needRestoreFocus) {
navigation.refocusFocusedItem();
that._scheduleNavigationFocusRestore(navigation)
}
return rowsArea
}
_scheduleNavigationFocusRestore(navigation) {
const onReady = () => {
this.off("contentReady", onReady);
if (!navigation.containsActiveElement()) {
navigation.refocusFocusedItem()
}
};
this.on("contentReady", onReady)
}
_renderColumnsArea(columnsAreaElement) {
const that = this;
const columnsArea = that._columnsArea || new HeadersArea.HorizontalHeadersArea(that);
that._columnsArea = columnsArea;
const navigation = that._getColumnsAreaNavigation();
const needRestoreFocus = navigation.containsActiveElement();
columnsArea.render(columnsAreaElement, that._dataController.getColumnsInfo());
navigation.updateTabIndexes();
if (needRestoreFocus) {
navigation.refocusFocusedItem();
that._scheduleNavigationFocusRestore(navigation)
}
return columnsArea
}
_initMarkup() {
super._initMarkup();
this.$element().addClass("dx-pivotgrid").attr("role", "group").attr("aria-label", localizationMessage.format("dxPivotGrid-ariaLabel"))
}
_renderContentImpl() {
const that = this;
let columnsAreaElement;
let rowsAreaElement;
let dataAreaElement;
let tableElement;
const isFirstDrawing = !that._pivotGridContainer;
let rowHeaderContainer;
let columnHeaderContainer;
let filterHeaderContainer;
let dataHeaderContainer;
tableElement = !isFirstDrawing && that._tableElement();
if (!tableElement) {
that.$element().addClass("dx-row-lines").addClass(FIELDS_CONTAINER_CLASS);
that._pivotGridContainer = $(DIV).addClass("dx-pivotgrid-container");
that._renderFieldChooser();
that._renderContextMenu();
columnsAreaElement = $(TD).addClass("dx-area-column-cell");
rowsAreaElement = $(TD).addClass("dx-area-row-cell");
dataAreaElement = $(TD).addClass("dx-area-data-cell");
tableElement = that._createTableElement();
dataHeaderContainer = $(TD).addClass("dx-data-header");
filterHeaderContainer = $("<td>").attr("colspan", "2").addClass("dx-filter-header");
columnHeaderContainer = $(TD).addClass("dx-column-header");
rowHeaderContainer = $(TD).addClass("dx-area-description-cell");
$(TR).append(filterHeaderContainer).appendTo(tableElement);
$(TR).append(dataHeaderContainer).append(columnHeaderContainer).appendTo(tableElement);
$(TR).append(rowHeaderContainer).append(columnsAreaElement).appendTo(tableElement);
$(TR).addClass("dx-bottom-row").append(rowsAreaElement).append(dataAreaElement).appendTo(tableElement);
that._pivotGridContainer.append(tableElement);
that.$element().append(that._pivotGridContainer);
if ("tree" === that.option("rowHeaderLayout")) {
rowsAreaElement.addClass("dx-area-tree-view")
}
}
that.$element().addClass("dx-overflow-hidden");
const fieldChooserBaseConfig = {
dataSource: that.getDataSource(),
encodeHtml: that.option("encodeHtml"),
allowFieldDragging: that.option("fieldPanel.allowFieldDragging"),
headerFilter: that.option("headerFilter"),
visible: that.option("visible"),
remoteSort: "virtual" === that.option("scrolling.mode")
};
if (that._fieldChooserBase) {
that._fieldChooserBase.option(fieldChooserBaseConfig)
} else {
that._fieldChooserBase = that._createComponent(that.$element(), FieldChooserBase, fieldChooserBaseConfig)
}
const dataArea = that._renderDataArea(dataAreaElement);
const rowsArea = that._renderRowsArea(rowsAreaElement);
const columnsArea = that._renderColumnsArea(columnsAreaElement);
dataArea.tableElement().prepend(columnsArea.headElement());
if (isFirstDrawing) {
that._renderLoadPanel(dataArea.groupElement().parent(), that.$element());
that._renderDescriptionArea();
rowsArea.renderScrollable();
columnsArea.renderScrollable();
dataArea.renderScrollable()
}
that._setAriaGridAttributes(dataArea, rowsArea, columnsArea);
[dataArea, rowsArea, columnsArea].forEach(area => {
unsubscribeScrollEvents(area)
});
that._renderHeaders(rowHeaderContainer, columnHeaderContainer, filterHeaderContainer, dataHeaderContainer);
that._update(isFirstDrawing)
}
_setAriaGridAttributes(dataArea, rowsArea, columnsArea) {
const $gridElement = dataArea.tableElement().parent();
const tableIds = [columnsArea.tableElement().attr("id"), rowsArea.tableElement().attr("id"), dataArea.tableElement().attr("id")].filter(isDefined).join(" ");
$gridElement.attr("role", "grid").attr("aria-owns", tableIds).attr("aria-rowcount", this._dataController.totalRowCount()).attr("aria-colcount", this._dataController.totalColumnCount())
}
_update(isFirstDrawing) {
const that