devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
528 lines (522 loc) • 23.2 kB
JavaScript
/**
* 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
};