UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

730 lines (729 loc) • 27.7 kB
/** * DevExtreme (esm/__internal/ui/m_drop_down_button.js) * Version: 24.2.6 * Build date: Mon Mar 17 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import _extends from "@babel/runtime/helpers/esm/extends"; import messageLocalization from "../../common/core/localization/message"; import registerComponent from "../../core/component_registrator"; import { getPublicElement } from "../../core/element"; import Guid from "../../core/guid"; import $ from "../../core/renderer"; import { FunctionTemplate } from "../../core/templates/function_template"; import { ensureDefined } from "../../core/utils/common"; import { compileGetter } from "../../core/utils/data"; import { Deferred } from "../../core/utils/deferred"; import { extend } from "../../core/utils/extend"; import { getImageContainer } from "../../core/utils/icon"; import { isDefined, isObject, isPlainObject } from "../../core/utils/type"; import DataController from "../../data_controller"; import ButtonGroup from "../../ui/button_group"; import List from "../../ui/list_light"; import Widget from "../core/widget/widget"; import { getElementWidth, getSizeValue } from "../ui/drop_down_editor/m_utils"; import Popup from "../ui/popup/m_popup"; const DROP_DOWN_BUTTON_CLASS = "dx-dropdownbutton"; const DROP_DOWN_BUTTON_CONTENT = "dx-dropdownbutton-content"; const DROP_DOWN_BUTTON_ACTION_CLASS = "dx-dropdownbutton-action"; const DROP_DOWN_BUTTON_TOGGLE_CLASS = "dx-dropdownbutton-toggle"; const DROP_DOWN_BUTTON_HAS_ARROW_CLASS = "dx-dropdownbutton-has-arrow"; const DROP_DOWN_BUTTON_POPUP_WRAPPER_CLASS = "dx-dropdownbutton-popup-wrapper"; const DROP_DOWN_EDITOR_OVERLAY_CLASS = "dx-dropdowneditor-overlay"; const DX_BUTTON_CLASS = "dx-button"; const DX_BUTTON_TEXT_CLASS = "dx-button-text"; const DX_ICON_RIGHT_CLASS = "dx-icon-right"; const OVERLAY_CONTENT_LABEL = "Dropdown"; class DropDownButton extends Widget { _getDefaultOptions() { return _extends({}, super._getDefaultOptions(), { itemTemplate: "item", keyExpr: "this", selectedItem: null, selectedItemKey: null, stylingMode: "outlined", deferRendering: true, noDataText: messageLocalization.format("dxCollectionWidget-noDataText"), useSelectMode: false, splitButton: false, showArrowIcon: true, template: null, text: "", type: "normal", onButtonClick: null, onSelectionChanged: null, onItemClick: null, opened: false, items: null, dataSource: null, focusStateEnabled: true, hoverStateEnabled: true, dropDownOptions: {}, dropDownContentTemplate: "content", wrapItemText: false, useItemTextAsTitle: true, grouped: false, groupTemplate: "group", buttonGroupOptions: {} }) } _setOptionsByReference() { super._setOptionsByReference(); extend(this._optionsByReference, { selectedItem: true }) } _init() { super._init(); this._createItemClickAction(); this._createActionClickAction(); this._createSelectionChangedAction(); this._initDataController(); this._compileKeyGetter(); this._compileDisplayGetter(); this._options.cache("buttonGroupOptions", this.option("buttonGroupOptions")); this._options.cache("dropDownOptions", this.option("dropDownOptions")) } _initDataController() { const dataSource = this.option("dataSource"); this._dataController = new DataController(dataSource ?? this.option("items"), { key: this.option("keyExpr") }) } _initTemplates() { this._templateManager.addDefaultTemplates({ content: new FunctionTemplate((options => { const $popupContent = $(options.container); const $listContainer = $("<div>").appendTo($popupContent); this._list = this._createComponent($listContainer, List, this._listOptions()); this._list.registerKeyHandler("escape", this._escHandler.bind(this)); this._list.registerKeyHandler("tab", this._escHandler.bind(this)); this._list.registerKeyHandler("leftArrow", this._escHandler.bind(this)); this._list.registerKeyHandler("rightArrow", this._escHandler.bind(this)) })) }); super._initTemplates() } _compileKeyGetter() { this._keyGetter = compileGetter(this._dataController.key()) } _compileDisplayGetter() { const { displayExpr: displayExpr } = this.option(); this._displayGetter = compileGetter(displayExpr) } _initMarkup() { super._initMarkup(); this.$element().addClass("dx-dropdownbutton"); this._renderButtonGroup(); this._updateArrowClass(); if (isDefined(this.option("selectedItemKey"))) { this._loadSelectedItem().done(this._updateActionButton.bind(this)) } } _renderFocusTarget() {} _render() { if (!this.option("deferRendering") || this.option("opened")) { this._renderPopup() } super._render() } _renderContentImpl() { if (this._popup) { this._renderPopupContent() } return super._renderContentImpl() } _loadSelectedItem() { var _this$_loadSingleDefe; null === (_this$_loadSingleDefe = this._loadSingleDeferred) || void 0 === _this$_loadSingleDefe || _this$_loadSingleDefe.reject(); const d = Deferred(); if (this._list && void 0 !== this._lastSelectedItemData) { const cachedResult = this.option("useSelectMode") ? this._list.option("selectedItem") : this._lastSelectedItemData; return d.resolve(cachedResult) } this._lastSelectedItemData = void 0; const selectedItemKey = this.option("selectedItemKey"); this._dataController.loadSingle(selectedItemKey).done(d.resolve).fail((() => { d.reject(null) })); this._loadSingleDeferred = d; return d.promise() } _createActionClickAction() { this._actionClickAction = this._createActionByOption("onButtonClick") } _createSelectionChangedAction() { this._selectionChangedAction = this._createActionByOption("onSelectionChanged") } _createItemClickAction() { this._itemClickAction = this._createActionByOption("onItemClick") } _fireSelectionChangedAction(_ref) { let { previousValue: previousValue, value: value } = _ref; this._selectionChangedAction({ item: value, previousItem: previousValue }) } _fireItemClickAction(_ref2) { let { event: event, itemElement: itemElement, itemData: itemData } = _ref2; return this._itemClickAction({ event: event, itemElement: itemElement, itemData: this._actionItem || itemData }) } _getButtonTemplate() { const { template: template, splitButton: splitButton, showArrowIcon: showArrowIcon } = this.option(); if (template) { return template } return splitButton || !showArrowIcon ? "content" : (_ref3, buttonContent) => { let { text: text, icon: icon } = _ref3; const $firstIcon = getImageContainer(icon); const $textContainer = text ? $("<span>").text(text).addClass("dx-button-text") : void 0; const $secondIcon = getImageContainer("spindown").addClass("dx-icon-right"); $(buttonContent).append($firstIcon, $textContainer, $secondIcon) } } _getActionButtonConfig() { const { icon: icon, text: text, type: type, splitButton: splitButton } = this.option(); const actionButtonConfig = { text: text, icon: icon, type: type, template: this._getButtonTemplate(), elementAttr: { class: "dx-dropdownbutton-action" } }; if (splitButton) { actionButtonConfig.elementAttr.role = "menuitem" } return actionButtonConfig } _getSpinButtonConfig() { const { type: type } = this.option(); const config = { type: type, icon: "spindown", elementAttr: { class: "dx-dropdownbutton-toggle", role: "menuitem" } }; return config } _getButtonGroupItems() { const { splitButton: splitButton } = this.option(); const items = [this._getActionButtonConfig()]; if (splitButton) { items.push(this._getSpinButtonConfig()) } return items } _buttonGroupItemClick(_ref4) { let { event: event, itemData: itemData } = _ref4; const isActionButton = "dx-dropdownbutton-action" === itemData.elementAttr.class; const isToggleButton = "dx-dropdownbutton-toggle" === itemData.elementAttr.class; if (isToggleButton) { this.toggle() } else if (isActionButton) { this._actionClickAction({ event: event, selectedItem: this.option("selectedItem") }); if (!this.option("splitButton")) { this.toggle() } } } _getButtonGroupOptions() { const { accessKey: accessKey, focusStateEnabled: focusStateEnabled, hoverStateEnabled: hoverStateEnabled, splitButton: splitButton, stylingMode: stylingMode, tabIndex: tabIndex } = this.option(); const buttonGroupOptions = _extends({ items: this._getButtonGroupItems(), width: "100%", height: "100%", selectionMode: "none", focusStateEnabled: focusStateEnabled, hoverStateEnabled: hoverStateEnabled, stylingMode: stylingMode, accessKey: accessKey, tabIndex: tabIndex, elementAttr: { role: splitButton ? "menu" : "group" }, onItemClick: this._buttonGroupItemClick.bind(this), onKeyboardHandled: e => this._keyboardHandler(e) }, this._options.cache("buttonGroupOptions")); return buttonGroupOptions } _renderPopupContent() { const $content = this._popup.$content(); const template = this._getTemplateByOption("dropDownContentTemplate"); $content.empty(); this._popupContentId = `dx-${new Guid}`; this.setAria("id", this._popupContentId, $content); return template.render({ container: getPublicElement($content), model: this.option("items") || this._dataController.getDataSource() }) } _popupOptions() { const horizontalAlignment = this.option("rtlEnabled") ? "right" : "left"; return extend({ dragEnabled: false, focusStateEnabled: false, deferRendering: this.option("deferRendering"), hideOnOutsideClick: e => { const $element = this.$element(); const $buttonClicked = $(e.target).closest(".dx-dropdownbutton"); return !$buttonClicked.is($element) }, showTitle: false, animation: { show: { type: "fade", duration: 0, from: 0, to: 1 }, hide: { type: "fade", duration: 400, from: 1, to: 0 } }, _ignoreFunctionValueDeprecation: true, width: () => getElementWidth(this.$element()), height: "auto", shading: false, position: { of: this.$element(), collision: "flipfit", my: `${horizontalAlignment} top`, at: `${horizontalAlignment} bottom` }, _wrapperClassExternal: "dx-dropdowneditor-overlay" }, this._options.cache("dropDownOptions"), { visible: this.option("opened") }) } _listOptions() { const selectedItemKey = this.option("selectedItemKey"); const useSelectMode = this.option("useSelectMode"); return { selectionMode: useSelectMode ? "single" : "none", wrapItemText: this.option("wrapItemText"), focusStateEnabled: this.option("focusStateEnabled"), hoverStateEnabled: this.option("hoverStateEnabled"), useItemTextAsTitle: this.option("useItemTextAsTitle"), onContentReady: () => this._fireContentReadyAction(), selectedItemKeys: isDefined(selectedItemKey) && useSelectMode ? [selectedItemKey] : [], grouped: this.option("grouped"), groupTemplate: this.option("groupTemplate"), keyExpr: this._dataController.key(), noDataText: this.option("noDataText"), displayExpr: this.option("displayExpr"), itemTemplate: this.option("itemTemplate"), items: this.option("items"), dataSource: this._dataController.getDataSource(), onItemClick: e => { if (!this.option("useSelectMode")) { this._lastSelectedItemData = e.itemData } this.option("selectedItemKey", this._keyGetter(e.itemData)); const actionResult = this._fireItemClickAction(e); if (false !== actionResult) { this.toggle(false); this._buttonGroup.focus() } } } } _upDownKeyHandler() { var _this$_popup; if (null !== (_this$_popup = this._popup) && void 0 !== _this$_popup && _this$_popup.option("visible") && this._list) { this._list.focus() } else { this.open() } return true } _escHandler() { this.close(); this._buttonGroup.focus(); return true } _tabHandler() { this.close(); return true } _renderPopup() { const $popup = $("<div>"); this.$element().append($popup); this._popup = this._createComponent($popup, Popup, this._popupOptions()); this._popup.$content().addClass(DROP_DOWN_BUTTON_CONTENT); this._popup.$wrapper().addClass("dx-dropdownbutton-popup-wrapper"); this._popup.$overlayContent().attr("aria-label", "Dropdown"); this._popup.on("hiding", this._popupHidingHandler.bind(this)); this._popup.on("showing", this._popupShowingHandler.bind(this)); this._bindInnerWidgetOptions(this._popup, "dropDownOptions") } _popupHidingHandler() { this.option("opened", false); this._updateAriaAttributes(false) } _popupOptionChanged(args) { const options = Widget.getOptionsFromContainer(args); this._setPopupOption(options); const optionsKeys = Object.keys(options); if (optionsKeys.includes("width") || optionsKeys.includes("height")) { this._dimensionChanged() } } _dimensionChanged() { const popupWidth = getSizeValue(this.option("dropDownOptions.width")); if (void 0 === popupWidth) { this._setPopupOption("width", (() => getElementWidth(this.$element()))) } } _setPopupOption(optionName, value) { this._setWidgetOption("_popup", arguments) } _popupShowingHandler() { this.option("opened", true); this._updateAriaAttributes(true) } _setElementAria(value) { const elementAria = { owns: value ? this._popupContentId : void 0 }; this.setAria(elementAria, this.$element()) } _setButtonsAria(value) { const commonButtonAria = { expanded: value, haspopup: "listbox" }; const firstButtonAria = {}; if (!this.option("text")) { firstButtonAria.label = "dropdownbutton" } this._getButtons().each(((index, $button) => { if (0 === index) { this.setAria(_extends({}, firstButtonAria, commonButtonAria), $($button)) } else { this.setAria(commonButtonAria, $($button)) } })) } _updateAriaAttributes(value) { this._setElementAria(value); this._setButtonsAria(value) } _getButtons() { return this._buttonGroup.$element().find(".dx-button") } _renderButtonGroup() { var _this$_buttonGroup; const $buttonGroup = (null === (_this$_buttonGroup = this._buttonGroup) || void 0 === _this$_buttonGroup ? void 0 : _this$_buttonGroup.$element()) || $("<div>"); if (!this._buttonGroup) { this.$element().append($buttonGroup) } this._buttonGroup = this._createComponent($buttonGroup, ButtonGroup, this._getButtonGroupOptions()); this._buttonGroup.registerKeyHandler("downArrow", this._upDownKeyHandler.bind(this)); this._buttonGroup.registerKeyHandler("tab", this._tabHandler.bind(this)); this._buttonGroup.registerKeyHandler("upArrow", this._upDownKeyHandler.bind(this)); this._buttonGroup.registerKeyHandler("escape", this._escHandler.bind(this)); this._bindInnerWidgetOptions(this._buttonGroup, "buttonGroupOptions"); this._updateAriaAttributes(this.option("opened")) } _updateArrowClass() { const hasArrow = this.option("splitButton") || this.option("showArrowIcon"); this.$element().toggleClass("dx-dropdownbutton-has-arrow", hasArrow) } toggle(visible) { var _this$_popup2; if (!this._popup) { this._renderPopup(); this._renderContent() } return null === (_this$_popup2 = this._popup) || void 0 === _this$_popup2 ? void 0 : _this$_popup2.toggle(visible) } open() { return this.toggle(true) } close() { return this.toggle(false) } _setListOption(name, value) { var _this$_list; null === (_this$_list = this._list) || void 0 === _this$_list || _this$_list.option(name, value) } _getDisplayValue(item) { const isPrimitiveItem = !isObject(item); const displayValue = isPrimitiveItem ? item : this._displayGetter(item); return !isObject(displayValue) ? String(ensureDefined(displayValue, "")) : "" } _updateActionButton(selectedItem) { if (this.option("useSelectMode")) { this.option({ text: this._getDisplayValue(selectedItem), icon: isPlainObject(selectedItem) ? selectedItem.icon : void 0 }) } this._setOptionWithoutOptionChange("selectedItem", selectedItem); this._setOptionWithoutOptionChange("selectedItemKey", this._keyGetter(selectedItem)) } _clean() { var _this$_list2, _this$_popup3; null === (_this$_list2 = this._list) || void 0 === _this$_list2 || _this$_list2.$element().remove(); null === (_this$_popup3 = this._popup) || void 0 === _this$_popup3 || _this$_popup3.$element().remove() } _selectedItemKeyChanged(value) { this._setListOption("selectedItemKeys", this.option("useSelectMode") && isDefined(value) ? [value] : []); const previousItem = this.option("selectedItem"); this._loadSelectedItem().always((selectedItem => { this._updateActionButton(selectedItem); if (this._displayGetter(previousItem) !== this._displayGetter(selectedItem)) { this._fireSelectionChangedAction({ previousValue: previousItem, value: selectedItem }) } })) } _updateButtonGroup(name, value) { this._buttonGroup.option(name, value); this._updateAriaAttributes(this.option("opened")) } _actionButtonOptionChanged(_ref5) { let { name: name, value: value } = _ref5; const newConfig = {}; newConfig[name] = value; this._updateButtonGroup("items[0]", extend({}, this._getActionButtonConfig(), newConfig)); this._popup && this._popup.repaint() } _selectModeChanged(value) { if (value) { this._setListOption("selectionMode", "single"); const selectedItemKey = this.option("selectedItemKey"); this._setListOption("selectedItemKeys", isDefined(selectedItemKey) ? [selectedItemKey] : []); this._selectedItemKeyChanged(this.option("selectedItemKey")) } else { this._setListOption("selectionMode", "none"); this.option({ selectedItemKey: void 0, selectedItem: void 0 }); this._actionButtonOptionChanged({ text: this.option("text") }) } } _updateItemCollection(optionName) { const selectedItemKey = this.option("selectedItemKey"); this._setListOption("selectedItem", null); this._setWidgetOption("_list", [optionName]); if (isDefined(selectedItemKey)) { this._loadSelectedItem().done((selectedItem => { this._setListOption("selectedItemKeys", [selectedItemKey]); this._setListOption("selectedItem", selectedItem) })).fail((error => { this._setListOption("selectedItemKeys", []) })).always(this._updateActionButton.bind(this)) } } _updateDataController(items) { this._dataController.updateDataSource(items, this.option("keyExpr")); this._updateKeyExpr() } _updateKeyExpr() { this._compileKeyGetter(); this._setListOption("keyExpr", this._dataController.key()) } focus() { this._buttonGroup.focus() } _optionChanged(args) { var _this$_popup4; const { name: name, value: value } = args; switch (name) { case "useSelectMode": this._selectModeChanged(value); break; case "splitButton": this._updateArrowClass(); this._renderButtonGroup(); break; case "displayExpr": this._compileDisplayGetter(); this._setListOption(name, value); this._updateActionButton(this.option("selectedItem")); break; case "keyExpr": this._updateDataController(); break; case "buttonGroupOptions": this._innerWidgetOptionChanged(this._buttonGroup, args); break; case "dropDownOptions": if ("dropDownOptions.visible" === args.fullName) { break } if (void 0 !== args.value.visible) { delete args.value.visible } this._popupOptionChanged(args); this._innerWidgetOptionChanged(this._popup, args); break; case "opened": this.toggle(value); break; case "focusStateEnabled": case "hoverStateEnabled": this._setListOption(name, value); this._updateButtonGroup(name, value); super._optionChanged(args); break; case "items": this._updateDataController(this.option("items")); this._updateItemCollection(name); break; case "dataSource": this._dataController.updateDataSource(value); this._updateKeyExpr(); this._updateItemCollection(name); break; case "icon": case "text": this._actionButtonOptionChanged(args); break; case "showArrowIcon": this._updateArrowClass(); this._renderButtonGroup(); this._popup && this._popup.repaint(); break; case "width": case "height": super._optionChanged(args); null === (_this$_popup4 = this._popup) || void 0 === _this$_popup4 || _this$_popup4.repaint(); break; case "stylingMode": case "tabIndex": this._updateButtonGroup(name, value); break; case "type": this._updateButtonGroup("items", this._getButtonGroupItems()); break; case "itemTemplate": case "grouped": case "noDataText": case "groupTemplate": case "wrapItemText": case "useItemTextAsTitle": this._setListOption(name, value); break; case "dropDownContentTemplate": this._renderContent(); break; case "selectedItemKey": this._selectedItemKeyChanged(value); break; case "selectedItem": break; case "onItemClick": this._createItemClickAction(); break; case "onButtonClick": this._createActionClickAction(); break; case "onSelectionChanged": this._createSelectionChangedAction(); break; case "deferRendering": { const { opened: opened } = this.option(); this.toggle(opened); break } case "template": this._renderButtonGroup(); break; default: super._optionChanged(args) } } getDataSource() { return this._dataController.getDataSource() } } registerComponent("dxDropDownButton", DropDownButton); export default DropDownButton;