UNPKG

devextreme

Version:

JavaScript/TypeScript Component Suite for Responsive Web Development

528 lines (522 loc) • 23.2 kB
/** * DevExtreme (cjs/__internal/grids/pivot_grid/field_chooser/m_field_chooser_base.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/ */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.FieldChooserBase = void 0; var _click = require("../../../../common/core/events/click"); var _events_engine = _interopRequireDefault(require("../../../../common/core/events/core/events_engine")); var _index = require("../../../../common/core/events/utils/index"); var _message = _interopRequireDefault(require("../../../../common/core/localization/message")); var _array_store = _interopRequireDefault(require("../../../../common/data/array_store")); var _component_registrator = _interopRequireDefault(require("../../../../core/component_registrator")); var _renderer = _interopRequireDefault(require("../../../../core/renderer")); var _deferred = require("../../../../core/utils/deferred"); var _extend = require("../../../../core/utils/extend"); var _iterator = require("../../../../core/utils/iterator"); var _type = require("../../../../core/utils/type"); var _ui = _interopRequireDefault(require("../../../../ui/widget/ui.widget")); var _m_column_state_mixin = _interopRequireDefault(require("../../../grids/grid_core/column_state_mixin/m_column_state_mixin")); var _m_header_filter_core = require("../../../grids/grid_core/header_filter/m_header_filter_core"); var _m_utils = _interopRequireDefault(require("../../../grids/grid_core/m_utils")); var _m_sorting_mixin = _interopRequireDefault(require("../../../grids/grid_core/sorting/m_sorting_mixin")); var _roving_tab_index = require("../keyboard_navigation/roving_tab_index"); var _m_widget_utils = require("../m_widget_utils"); var _m_sortable = _interopRequireDefault(require("../sortable/m_sortable")); var _const = require("./const"); var _dom = require("./dom"); var _utils = require("./utils"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } const { Sortable: Sortable } = _m_sortable.default; const DIV = "<div>"; const FIELD_AREAS = ["filter", "row", "column", "data"]; const FIELD_NAVIGATION_DELTAS = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 }; function isFieldNavigationEvent(e) { return "keydown" === e.type && void 0 !== FIELD_NAVIGATION_DELTAS[e.key] && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey } class HeaderFilterView extends _m_header_filter_core.HeaderFilterView { _getSearchExpr(options, headerFilterOptions) { options.useDefaultSearchExpr = true; return super._getSearchExpr(options, headerFilterOptions) } } const processItems = function(groupItems, field) { const filterValues = []; const isTree = !!field.groupName; const isExcludeFilterType = "exclude" === field.filterType; if (field.filterValues) { (0, _iterator.each)(field.filterValues, (_, filterValue) => { filterValues.push(Array.isArray(filterValue) ? filterValue.join("/") : null === filterValue || void 0 === filterValue ? void 0 : filterValue.valueOf()) }) }(0, _m_widget_utils.foreachTree)(groupItems, items => { var _item$value; const item = items[0]; const path = (0, _m_widget_utils.createPath)(items); const preparedFilterValueByText = isTree ? (0, _iterator.map)(items, item => item.text).reverse().join("/") : item.text; item.value = isTree ? path.slice(0) : item.key || item.value; const preparedFilterValue = isTree ? path.join("/") : null === (_item$value = item.value) || void 0 === _item$value ? void 0 : _item$value.valueOf(); if (item.children) { item.items = item.children; item.children = null }(0, _m_header_filter_core.updateHeaderFilterItemSelectionState)(item, item.key && filterValues.includes(preparedFilterValueByText) || filterValues.includes(preparedFilterValue), isExcludeFilterType) }) }; function getMainGroupField(dataSource, sourceField) { let field = sourceField; if ((0, _type.isDefined)(sourceField.groupIndex)) { field = dataSource.getAreaFields(sourceField.area, true)[sourceField.areaIndex] } return field } function fieldHasHeaderFilter(dataSource, field) { const mainGroupField = getMainGroupField(dataSource, field); const isFirstInGroup = 0 === field.groupIndex || !(0, _type.isDefined)(field.groupIndex); return mainGroupField.allowFiltering && isFirstInGroup && "data" !== field.area } function getStringState(state) { state = state || {}; return JSON.stringify([state.fields, state.columnExpandedPaths, state.rowExpandedPaths]) } const mixinWidget = (0, _m_sorting_mixin.default)((0, _m_column_state_mixin.default)(_ui.default)); class FieldChooserBase extends mixinWidget { constructor() { super(...arguments); this._focusedFieldIndex = -1 } _getDefaultOptions() { return Object.assign({}, super._getDefaultOptions(), { allowFieldDragging: true, applyChangesMode: "instantly", state: void 0, headerFilter: { width: 252, height: 325, allowSelectAll: true, showRelevantValues: false, search: { enabled: false, timeout: 500, editorOptions: {}, mode: "contains" }, texts: { emptyValue: _message.default.format("dxDataGrid-headerFilterEmptyValue"), ok: _message.default.format("dxDataGrid-headerFilterOK"), cancel: _message.default.format("dxDataGrid-headerFilterCancel") } }, remoteSort: false }) } _setAriaSortAttribute(_column, _ariaSortState, _$rootElement) {} _init() { super._init(); this._headerFilterView = new HeaderFilterView(this); this._refreshDataSource(); this.subscribeToEvents(); _m_utils.default.logHeaderFilterDeprecatedWarningIfNeed(this) } _refreshDataSource() { const dataSource = this.option("dataSource"); this._resetFieldNavigation(); if (null !== dataSource && void 0 !== dataSource && dataSource.fields && dataSource.load) { this._dataSource = dataSource } } _resetFieldNavigation() { Object.values(this._fieldNavigationMap ?? {}).forEach(navigation => { navigation.reset() }) } _optionChanged(args) { switch (args.name) { case "dataSource": this._refreshDataSource(); break; case "applyChangesMode": case "remoteSort": break; case "state": if (this._skipStateChange || !this._dataSource) { break } if ("instantly" === this.option("applyChangesMode") && getStringState(this._dataSource.state()) !== getStringState(args.value)) { this._dataSource.state(args.value) } else { this._clean(true); this._renderComponent() } break; case "headerFilter": case "allowFieldDragging": this._invalidate(); break; default: super._optionChanged(args) } } renderField(field, showColumnLines) { const that = this; const caption = field.caption ?? field.dataField ?? ""; const $fieldContent = (0, _renderer.default)(DIV).addClass(_const.CLASSES.area.fieldContent).text(caption); const $fieldElement = (0, _renderer.default)(DIV).addClass(_const.CLASSES.area.field).addClass(_const.CLASSES.area.box).attr("tabIndex", 0).data("field", field).append($fieldContent); if ("data" !== field.area) { if (field.allowSorting) { that._applyColumnState({ name: "sort", rootElement: $fieldElement, column: { alignment: that.option("rtlEnabled") ? "right" : "left", sortOrder: "desc" === field.sortOrder ? "desc" : "asc", allowSorting: field.allowSorting, caption: caption }, showColumnLines: showColumnLines }) } that._renderHeaderFilterIcon($fieldElement, field, showColumnLines) } if (field.groupName) { $fieldElement.attr(_const.ATTRIBUTES.itemGroup, field.groupName) } return $fieldElement } _clean(value) {} _render() { super._render(); this._headerFilterView.render(this.$element()) } renderSortable() { const that = this; that._createComponent(that.$element(), Sortable, (0, _extend.extend)({ allowDragging: that.option("allowFieldDragging"), itemSelector: `.${_const.CLASSES.area.field}`, itemContainerSelector: `.${_const.CLASSES.area.fieldContainer}`, groupSelector: `.${_const.CLASSES.area.fieldList}`, groupFilter() { const dataSource = that._dataSource; const $sortable = (0, _renderer.default)(this).closest(".dx-sortable-old"); const pivotGrid = $sortable.data("dxPivotGrid"); const pivotGridFieldChooser = $sortable.data("dxPivotGridFieldChooser"); if (pivotGrid) { return pivotGrid.getDataSource() === dataSource } if (pivotGridFieldChooser) { return pivotGridFieldChooser.option("dataSource") === dataSource } return false }, itemRender: _dom.dragAndDropItemRender, onDragging(e) { const field = e.sourceElement.data("field"); const { targetGroup: targetGroup } = e; e.cancel = (0, _utils.shouldCancelDragging)(field, targetGroup) }, useIndicator: true, onChanged(e) { const field = e.sourceElement.data("field"); e.removeSourceElement = !!e.sourceGroup; that._adjustSortableOnChangedArgs(e); if (field) { const { targetIndex: targetIndex } = e; let mainGroupField; let invisibleFieldsIndexOffset = 0; that._processDemandState(dataSource => { const fields = dataSource.getAreaFields(field.area, true); mainGroupField = getMainGroupField(dataSource, field); const visibleFields = fields.filter(f => false !== f.visible); const fieldBeforeTarget = visibleFields[targetIndex - 1]; if (fieldBeforeTarget) { invisibleFieldsIndexOffset = fields.filter(f => false === f.visible && f.areaIndex <= fieldBeforeTarget.areaIndex).length } }); that._applyChanges([mainGroupField], { area: e.targetGroup, areaIndex: targetIndex + invisibleFieldsIndexOffset }) } } }, that._getSortableOptions())) } _processDemandState(func) { const that = this; const isInstantlyMode = "instantly" === that.option("applyChangesMode"); const dataSource = that._dataSource; if (isInstantlyMode) { func(dataSource, isInstantlyMode) } else { const currentState = dataSource.state(); const pivotGridState = that.option("state"); if (pivotGridState) { dataSource.state(pivotGridState, true) } func(dataSource, isInstantlyMode); dataSource.state(currentState, true) } } _applyChanges(fields, props) { const that = this; that._processDemandState((dataSource, isInstantlyMode) => { fields.forEach(_ref => { let { index: index } = _ref; dataSource.field(index, props) }); if (isInstantlyMode) { dataSource.load() } else { that._changedHandler() } }) } _applyLocalSortChanges(fieldIdx, sortOrder) { this._processDemandState(dataSource => { dataSource.field(fieldIdx, { sortOrder: sortOrder }); dataSource.sortLocal() }) } _adjustSortableOnChangedArgs(e) { e.removeSourceElement = false; e.removeTargetElement = true; e.removeSourceClass = false } _getSortableOptions() { return { direction: "auto" } } subscribeToEvents(element) { const fieldSelector = `.${_const.CLASSES.area.field}.${_const.CLASSES.area.box}`; const targetElement = element ?? this.$element(); const handler = e => { const field = (0, _renderer.default)(e.currentTarget).data("field"); if (!field) { return } const isClick = e.type === _click.name || "keydown" === e.type && ("Enter" === e.key || " " === e.key); const isAltArrowDown = "keydown" === e.type && e.altKey && "ArrowDown" === e.key; const isHeaderFilterIconInteraction = isClick && (0, _renderer.default)(e.target).hasClass(_const.CLASSES.headerFilter); if (isAltArrowDown || isHeaderFilterIconInteraction) { if (fieldHasHeaderFilter(this._dataSource, field)) { e.preventDefault(); this.handleHeaderFilterIconClick(e, field) } return } if (isFieldNavigationEvent(e)) { this._handleFieldNavigation(e, field); return } if (!isClick) { return } if (field.allowSorting && "data" !== field.area) { e.preventDefault(); this.handleFieldClick(field) } }; _events_engine.default.on(targetElement, (0, _index.addNamespace)(_click.name, "dxPivotGridFieldChooserBase"), fieldSelector, handler); _events_engine.default.on(targetElement, (0, _index.addNamespace)("keydown", "dxPivotGridFieldChooserBase"), fieldSelector, handler); _events_engine.default.on(targetElement, (0, _index.addNamespace)("focusin", "dxPivotGridFieldChooserBase"), fieldSelector, e => { const $field = (0, _renderer.default)(e.currentTarget); const field = $field.data("field"); if (!field) { return } this._focusedFieldIndex = field.index; if (field.area) { this._getFieldNavigation(field.area).handleFocusIn(e.currentTarget) } }); _events_engine.default.on(targetElement, (0, _index.addNamespace)("focusout", "dxPivotGridFieldChooserBase"), fieldSelector, e => { const relatedTarget = e.relatedTarget; if (relatedTarget) { this._focusedFieldIndex = -1 } }) } restoreFieldFocus() { this.updateFieldsTabIndexes(); if (-1 !== this._focusedFieldIndex) { this.focusFieldElement(this._focusedFieldIndex) } } updateFieldsTabIndexes() { FIELD_AREAS.forEach(area => { this._getFieldNavigation(area).updateTabIndexes() }) } _getFieldNavigation(area) { this._fieldNavigationMap = this._fieldNavigationMap ?? {}; this._fieldNavigationMap[area] = this._fieldNavigationMap[area] ?? new _roving_tab_index.RovingTabIndex({ component: this, getItems: () => this._getAreaFieldElements(area), scrollToItem: item => this._scrollFieldElementToView(item), getItemId: item => this._getFieldElementId(item) }); return this._fieldNavigationMap[area] } _getFieldElementId(fieldElement) { const field = (0, _renderer.default)(fieldElement).data("field"); return (0, _type.isDefined)(null === field || void 0 === field ? void 0 : field.index) ? String(field.index) : void 0 } _getAreaFieldElements(area) { const ownFieldChooserRoot = this.$element().closest(`.${_const.CLASSES.fieldChooser.self}`).get(0) ?? null; const fields = this.$element().find(`[group="${area}"] .${_const.CLASSES.area.field}.${_const.CLASSES.area.box}`).toArray(); return fields.filter(field => { const fieldChooserRoot = (0, _renderer.default)(field).closest(`.${_const.CLASSES.fieldChooser.self}`).get(0) ?? null; return fieldChooserRoot === ownFieldChooserRoot }) } _scrollFieldElementToView(fieldElement) { const $scrollable = (0, _renderer.default)(fieldElement).closest(`.${_const.CLASSES.scrollable.self}`); const scrollable = $scrollable.length ? $scrollable.dxScrollable("instance") : void 0; null === scrollable || void 0 === scrollable || scrollable.scrollToElement(fieldElement) } _getFieldNavigationDelta(key) { const isHorizontal = "ArrowLeft" === key || "ArrowRight" === key; if (isHorizontal && this.option("rtlEnabled")) { return -FIELD_NAVIGATION_DELTAS[key] } return FIELD_NAVIGATION_DELTAS[key] } _handleFieldNavigation(e, field) { if (!field.area) { return } const navigation = this._getFieldNavigation(field.area); const items = navigation.getItems(); const index = items.indexOf(e.currentTarget); if (index < 0) { return } e.preventDefault(); const targetIndex = index + this._getFieldNavigationDelta(e.key); if (targetIndex >= 0 && targetIndex < items.length) { navigation.focusItem(targetIndex) } } _renderHeaderFilterIcon($fieldElement, field, showColumnLines) { var _mainGroupField$filte; if (!fieldHasHeaderFilter(this._dataSource, field)) { return } const mainGroupField = getMainGroupField(this._dataSource, field); const isEmpty = !(null !== (_mainGroupField$filte = mainGroupField.filterValues) && void 0 !== _mainGroupField$filte && _mainGroupField$filte.length); const $icon = (0, _renderer.default)("<span>").addClass(_const.CLASSES.headerFilter).toggleClass("dx-header-filter-empty", isEmpty); let $container = this._getIndicatorContainer($fieldElement); if (!$container.length) { const rtlEnabled = this.option("rtlEnabled"); const indicatorAlignment = rtlEnabled ? "left" : "right"; $container = (0, _renderer.default)("<div>").addClass("dx-column-indicators"); $container.css("float", showColumnLines ? indicatorAlignment : null); $container[!showColumnLines && rtlEnabled ? "appendTo" : "prependTo"]($fieldElement) } $container.append($icon) } handleHeaderFilterIconClick(e, field) { const that = this; const mainGroupField = (0, _extend.extend)(true, {}, getMainGroupField(that._dataSource, field)); const type = mainGroupField.groupName ? "tree" : "list"; const paginate = this._dataSource.paginate() && "list" === type; this._headerFilterView.showHeaderFilterMenu((0, _renderer.default)(e.currentTarget), (0, _extend.extend)(mainGroupField, { type: type, encodeHtml: this.option("encodeHtml"), dataSource: { useDefaultSearch: !paginate, load(options) { const { userData: userData } = options; if (userData.store) { return userData.store.load(options) } const d = new _deferred.Deferred; that._dataSource.getFieldValues(mainGroupField.index, that.option("headerFilter.showRelevantValues"), paginate ? options : void 0).done(data => { const emptyValue = that.option("headerFilter.texts.emptyValue"); data.forEach(element => { if (!element.text) { element.text = emptyValue } }); if (paginate) { d.resolve(data) } else { userData.store = new _array_store.default(data); userData.store.load(options).done(d.resolve).fail(d.reject) } }).fail(d.reject); return d }, postProcess(data) { processItems(data, mainGroupField); return data } }, onHidden: () => { this.focusFieldElement(field.index) }, apply() { that._focusedFieldIndex = field.index; that._applyChanges([mainGroupField], { filterValues: this.filterValues, filterType: this.filterType }) } })) } handleFieldClick(field) { const isRemoteSort = this.option("remoteSort"); const sortOrder = (0, _utils.reverseSortOrder)(field.sortOrder); if (isRemoteSort) { this._applyChanges([field], { sortOrder: sortOrder }) } else { this._applyLocalSortChanges(field.index, sortOrder) } } focusFieldElement(fieldIndex) { const fieldElements = this.$element().find(`.${_const.CLASSES.area.field}.${_const.CLASSES.area.box}`).get(); null === fieldElements || void 0 === fieldElements || fieldElements.forEach(fieldElement => { const field = (0, _renderer.default)(fieldElement).data("field"); if (field.index === fieldIndex) { fieldElement.focus() } }) } _initTemplates() {} addWidgetPrefix(className) { return `dx-pivotgrid-${className}` } } exports.FieldChooserBase = FieldChooserBase; (0, _component_registrator.default)("dxPivotGridFieldChooserBase", FieldChooserBase); var _default = exports.default = { FieldChooserBase: FieldChooserBase };