UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

843 lines (696 loc) • 27 kB
"use strict"; var $ = require("../../core/renderer"), commonUtils = require("../../core/utils/common"), typeUtils = require("../../core/utils/type"), each = require("../../core/utils/iterator").each, extend = require("../../core/utils/extend").extend, inkRipple = require("../widget/utils.ink_ripple"), HierarchicalCollectionWidget = require("../hierarchical_collection/ui.hierarchical_collection_widget"), MenuBaseEditStrategy = require("./ui.menu_base.edit.strategy"), devices = require("../../core/devices"), themes = require("../themes"); var DX_MENU_CLASS = "dx-menu", DX_MENU_NO_ICONS_CLASS = DX_MENU_CLASS + "-no-icons", DX_MENU_BASE_CLASS = "dx-menu-base", ITEM_CLASS = DX_MENU_CLASS + "-item", DX_ITEM_CONTENT_CLASS = ITEM_CLASS + "-content", DX_MENU_SELECTED_ITEM_CLASS = ITEM_CLASS + "-selected", DX_MENU_ITEM_WRAPPER_CLASS = ITEM_CLASS + "-wrapper", DX_MENU_ITEMS_CONTAINER_CLASS = DX_MENU_CLASS + "-items-container", DX_MENU_ITEM_EXPANDED_CLASS = ITEM_CLASS + "-expanded", DX_MENU_SEPARATOR_CLASS = DX_MENU_CLASS + "-separator", DX_MENU_ITEM_LAST_GROUP_ITEM = DX_MENU_CLASS + "-last-group-item", DX_ITEM_HAS_TEXT = ITEM_CLASS + "-has-text", DX_ITEM_HAS_ICON = ITEM_CLASS + "-has-icon", DX_ITEM_HAS_SUBMENU = ITEM_CLASS + "-has-submenu", DX_MENU_ITEM_POPOUT_CLASS = ITEM_CLASS + "-popout", DX_MENU_ITEM_POPOUT_CONTAINER_CLASS = DX_MENU_ITEM_POPOUT_CLASS + "-container", DX_MENU_ITEM_CAPTION_CLASS = ITEM_CLASS + "-text", SINGLE_SELECTION_MODE = "single", DEFAULT_DELAY = { "show": 50, "hide": 300 }; /** * @name dxMenuBase * @publicName dxMenuBase * @type object * @inherits HierarchicalCollectionWidget * @hidden */ var MenuBase = HierarchicalCollectionWidget.inherit({ _getDefaultOptions: function _getDefaultOptions() { return extend(this.callBase(), { /** * @name dxMenuBaseOptions.items * @publicName items * @type Array<dxMenuBaseItemTemplate> */ items: [], /** * @name dxMenuBaseOptions.cssClass * @publicName cssClass * @type string * @default "" */ cssClass: "", /** * @name dxMenuBaseOptions.activeStateEnabled * @publicName activeStateEnabled * @type Boolean * @default true */ activeStateEnabled: true, /** * @name dxMenuBaseOptions.showSubmenuMode * @publicName showSubmenuMode * @type Object|Enums.ShowSubmenuMode * @default { name: "onHover", delay: { show: 0, hide: 0 } } */ showSubmenuMode: { /** * @name dxMenuBaseOptions.showSubmenuMode.name * @publicName name * @type Enums.ShowSubmenuMode * @default "onHover" */ name: "onHover", /** * @name dxMenuBaseOptions.showSubmenuMode.delay * @publicName delay * @type Object|number * @default { show: 50, hide: 300 } */ delay: { /** * @name dxMenuBaseOptions.showSubmenuMode.delay.show * @publicName show * @type number * @default 50 */ show: 50, /** * @name dxMenuBaseOptions.showSubmenuMode.delay.hide * @publicName hide * @type number * @default 300 */ hide: 300 } }, /** * @name dxMenuBaseOptions.animation * @publicName animation * @type object * @default { show: { type: "fade", from: 0, to: 1, duration: 100 }, hide: { type: "fade", from: 1, to: 0, duration: 100 } } * @ref */ animation: { /** * @name dxMenuBaseOptions.animation.show * @publicName show * @type animationConfig * @default { type: "fade", from: 0, to: 1, duration: 100 } */ show: { type: "fade", from: 0, to: 1, duration: 100 }, /** * @name dxMenuBaseOptions.animation.hide * @publicName hide * @type animationConfig * @default { type: "fade", from: 1, to: 0, duration: 100 } */ hide: { type: "fade", from: 1, to: 0, duration: 100 } }, /** * @name dxMenuBaseOptions.selectByClick * @publicName selectByClick * @type boolean * @default false */ selectByClick: false, focusOnSelectedItem: false, /** * @name dxMenuBaseOptions.onItemHold * @publicName onItemHold * @hidden * @action * @inheritdoc */ /** * @name dxMenuBaseOptions.itemHoldTimeout * @publicName itemHoldTimeout * @hidden * @inheritdoc */ /** * @name dxMenuBaseOptions.noDataText * @publicName noDataText * @hidden * @inheritdoc */ /** * @name dxMenuBaseOptions.selectedIndex * @publicName selectedIndex * @hidden * @inheritdoc */ /** * @name dxMenuBaseOptions.selectedItemKeys * @publicName selectedItemKeys * @hidden * @inheritdoc */ /** * @name dxMenuBaseOptions.keyExpr * @publicName keyExpr * @hidden * @inheritdoc */ keyExpr: null, /** * @name dxMenuBaseOptions.parentIdExpr * @publicName parentIdExpr * @hidden * @inheritdoc */ /** * @name dxMenuBaseOptions.expandedExpr * @publicName expandedExpr * @hidden * @inheritdoc */ /** * @name dxMenuBaseItemTemplate * @publicName dxMenuBaseItemTemplate * @inherits CollectionWidgetItemTemplate * @type object */ /** * @name dxMenuBaseItemTemplate.beginGroup * @publicName beginGroup * @type Boolean */ /** * @name dxMenuBaseOptions.selectionMode * @publicName selectionMode * @type Enums.MenuSelectionMode * @default none */ _itemAttributes: { role: "menuitem" }, useInkRipple: false /** * @name dxMenuBaseItemTemplate.html * @publicName html * @type String * @hidden */ /** * @name dxMenuBaseItemTemplate.disabled * @publicName disabled * @type boolean * @default false */ /** * @name dxMenuBaseItemTemplate.visible * @publicName visible * @type boolean * @default true */ /** * @name dxMenuBaseItemTemplate.icon * @publicName icon * @type String */ /** * @name dxMenuBaseItemTemplate.text * @publicName text * @type String */ /** * @name dxMenuBaseItemTemplate.html * @publicName html * @type String */ /** * @name dxMenuBaseItemTemplate.items * @publicName items * @type Array<dxMenuBaseItemTemplate> */ /** * @name dxMenuBaseItemTemplate.selectable * @publicName selectable * @type boolean * @default false */ /** * @name dxMenuBaseItemTemplate.selected * @publicName selected * @type boolean * @default false */ /** * @name dxMenuBaseItemTemplate.closeMenuOnClick * @publicName closeMenuOnClick * @type boolean * @default true */ }); }, _defaultOptionsRules: function _defaultOptionsRules() { return this.callBase().concat([{ device: function device() { return (/android5/.test(themes.current()) ); }, options: { useInkRipple: true } }]); }, _activeStateUnit: "." + ITEM_CLASS, _itemDataKey: function _itemDataKey() { return "dxMenuItemDataKey"; }, _itemClass: function _itemClass() { return ITEM_CLASS; }, _setAriaSelected: commonUtils.noop, _selectedItemClass: function _selectedItemClass() { return DX_MENU_SELECTED_ITEM_CLASS; }, _widgetClass: function _widgetClass() { return DX_MENU_BASE_CLASS; }, _focusTarget: function _focusTarget() { return this._itemContainer(); }, _clean: function _clean() { this.option("focusedElement", null); this.callBase(); }, _supportedKeys: function _supportedKeys() { var selectItem = function selectItem() { var $item = $(this.option("focusedElement")); if (!$item.length || !this._isSelectionEnabled()) { return; } this.selectItem($item[0]); }; return extend(this.callBase(), { space: selectItem, pageUp: commonUtils.noop, pageDown: commonUtils.noop }); }, _isSelectionEnabled: function _isSelectionEnabled() { return this.option("selectionMode") === SINGLE_SELECTION_MODE; }, _init: function _init() { this.callBase(); this._renderSelectedItem(); this._initActions(); }, _getTextContainer: function _getTextContainer(itemData) { var itemText = itemData.text, $itemContainer = $('<span>').addClass(DX_MENU_ITEM_CAPTION_CLASS), itemContent = typeUtils.isPlainObject(itemData) ? itemText : String(itemData); return itemText && $itemContainer.text(itemContent); }, _getPopoutContainer: function _getPopoutContainer(itemData) { var items = itemData.items, $popOutContainer; if (items && items.length) { var $popOutImage = $('<div>').addClass(DX_MENU_ITEM_POPOUT_CLASS); $popOutContainer = $('<span>').addClass(DX_MENU_ITEM_POPOUT_CONTAINER_CLASS).append($popOutImage); } return $popOutContainer; }, _getDataAdapterOptions: function _getDataAdapterOptions() { return { rootValue: 0, multipleSelection: false, recursiveSelection: false, recursiveExpansion: false, searchValue: "" }; }, _selectByItem: function _selectByItem(selectedItem) { if (!selectedItem) return; var nodeToSelect = this._dataAdapter.getNodeByItem(selectedItem); this._dataAdapter.toggleSelection(nodeToSelect.internalFields.key, true); }, _renderSelectedItem: function _renderSelectedItem() { var selectedKeys = this._dataAdapter.getSelectedNodesKeys(), selectedKey = selectedKeys.length && selectedKeys[0], selectedItem = this.option("selectedItem"); if (!selectedKey) { this._selectByItem(selectedItem); return; } var node = this._dataAdapter.getNodeByKey(selectedKey); if (node.selectable === false) return; if (!selectedItem) { this.option("selectedItem", node.internalFields.item); return; } if (selectedItem !== node.internalFields.item) { this._dataAdapter.toggleSelection(selectedKey, false); this._selectByItem(selectedItem); } }, _initActions: commonUtils.noop, _initMarkup: function _initMarkup() { this.callBase(); this._addCustomCssClass(this.$element()); this.option("useInkRipple") && this._renderInkRipple(); }, _renderInkRipple: function _renderInkRipple() { this._inkRipple = inkRipple.render(); }, _toggleActiveState: function _toggleActiveState($element, value, e) { this.callBase.apply(this, arguments); if (!this._inkRipple) { return; } var config = { element: $element, event: e }; if (value) { this._inkRipple.showWave(config); } else { this._inkRipple.hideWave(config); } }, _getShowSubmenuMode: function _getShowSubmenuMode() { var defaultValue = "onClick", optionValue = this.option("showSubmenuMode"); optionValue = typeUtils.isObject(optionValue) ? optionValue.name : optionValue; return this._isDesktopDevice() ? optionValue : defaultValue; }, _initSelectedItems: commonUtils.noop, _isDesktopDevice: function _isDesktopDevice() { return devices.real().deviceType === "desktop"; }, _initEditStrategy: function _initEditStrategy() { var Strategy = MenuBaseEditStrategy; this._editStrategy = new Strategy(this); }, _addCustomCssClass: function _addCustomCssClass($element) { $element.addClass(this.option("cssClass")); }, _itemWrapperSelector: function _itemWrapperSelector() { return "." + DX_MENU_ITEM_WRAPPER_CLASS; }, _hoverStartHandler: function _hoverStartHandler(e) { var that = this, $itemElement = that._getItemElementByEventArgs(e); if (!$itemElement || that._isItemDisabled($itemElement)) return; e.stopPropagation(); if (that._getShowSubmenuMode() === "onHover") { clearTimeout(this._showSubmenusTimeout); this._showSubmenusTimeout = setTimeout(that._showSubmenu.bind(that, $itemElement), that._getSubmenuDelay("show")); } }, _getAvailableItems: function _getAvailableItems($itemElements) { return this.callBase($itemElements).filter(function () { return $(this).css("visibility") !== "hidden"; }); }, _isItemDisabled: function _isItemDisabled($item) { return this._disabledGetter($item.data(this._itemDataKey())); }, _showSubmenu: function _showSubmenu($itemElement) { this._addExpandedClass($itemElement); }, _addExpandedClass: function _addExpandedClass(itemElement) { $(itemElement).addClass(DX_MENU_ITEM_EXPANDED_CLASS); }, _getSubmenuDelay: function _getSubmenuDelay(action) { var delay = this.option("showSubmenuMode").delay; if (!typeUtils.isDefined(delay)) { return DEFAULT_DELAY[action]; } return typeUtils.isObject(delay) ? delay[action] : delay; }, // TODO: try to simplify _getItemElementByEventArgs: function _getItemElementByEventArgs(eventArgs) { var $target = $(eventArgs.target); if ($target.hasClass(this._itemClass()) || $target.get(0) === eventArgs.currentTarget) { return $target; } // TODO: move it to inheritors, menuBase don't know about dx-submenu while (!$target.hasClass(this._itemClass())) { $target = $target.parent(); if ($target.hasClass("dx-submenu")) { return null; } } return $target; }, _hoverEndHandler: function _hoverEndHandler() { clearTimeout(this._showSubmenusTimeout); }, _hasSubmenu: function _hasSubmenu(node) { return node.internalFields.childrenKeys.length; }, _renderContentImpl: function _renderContentImpl() { this._renderItems(this._dataAdapter.getRootNodes()); }, _renderItems: function _renderItems(nodes, submenuContainer) { var that = this, $nodeContainer; if (nodes.length) { this.hasIcons = false; $nodeContainer = this._renderContainer(this.$element(), submenuContainer); each(nodes, function (index, node) { that._renderItem(index, node, $nodeContainer); }); if (!this.hasIcons) $nodeContainer.addClass(DX_MENU_NO_ICONS_CLASS); } }, _renderContainer: function _renderContainer($wrapper) { return $("<ul>").appendTo($wrapper).addClass(DX_MENU_ITEMS_CONTAINER_CLASS); }, _createDOMElement: function _createDOMElement($nodeContainer) { var $node = $("<li>").appendTo($nodeContainer).addClass(DX_MENU_ITEM_WRAPPER_CLASS); return $node; }, _renderItem: function _renderItem(index, node, $nodeContainer, $nodeElement) { var items = this.option("items"), $itemFrame; this._renderSeparator(node, index, $nodeContainer); if (node.internalFields.item.visible === false) return; var $node = $nodeElement || this._createDOMElement($nodeContainer); if (items[index + 1] && items[index + 1].beginGroup) { $node.addClass(DX_MENU_ITEM_LAST_GROUP_ITEM); } $itemFrame = this.callBase(index, node.internalFields.item, $node); if (node.internalFields.item === this.option("selectedItem")) { $itemFrame.addClass(DX_MENU_SELECTED_ITEM_CLASS); } $itemFrame.attr("tabIndex", -1); if (this._hasSubmenu(node)) this.setAria("haspopup", "true", $itemFrame); }, _renderItemFrame: function _renderItemFrame(index, itemData, $itemContainer) { var $itemFrame = $itemContainer.children("." + ITEM_CLASS); return $itemFrame.length ? $itemFrame : this.callBase.apply(this, arguments); }, _refreshItem: function _refreshItem($item, item) { var node = this._dataAdapter.getNodeByItem(item), index = $item.data(this._itemIndexKey()), $nodeContainer = $item.closest("ul"), $nodeElement = $item.closest("li"); this._renderItem(index, node, $nodeContainer, $nodeElement); }, _addContentClasses: function _addContentClasses(itemData, $itemFrame) { var hasText = itemData.text ? !!itemData.text.length : false, hasIcon = !!itemData.icon, hasSubmenu = itemData.items ? !!itemData.items.length : false; $itemFrame.toggleClass(DX_ITEM_HAS_TEXT, hasText); $itemFrame.toggleClass(DX_ITEM_HAS_ICON, hasIcon); if (!this.hasIcons) { this.hasIcons = hasIcon; } $itemFrame.toggleClass(DX_ITEM_HAS_SUBMENU, hasSubmenu); }, _getItemContent: function _getItemContent($itemFrame) { var $itemContent = this.callBase($itemFrame); if (!$itemContent.length) { $itemContent = $itemFrame.children("." + DX_ITEM_CONTENT_CLASS); } return $itemContent; }, _postprocessRenderItem: function _postprocessRenderItem(args) { var $itemElement = $(args.itemElement), selectedIndex = this._dataAdapter.getSelectedNodesKeys(), node; if (!selectedIndex.length || !this._selectedGetter(args.itemData) || !this._isItemSelectable(args.itemData)) { this._setAriaSelected($itemElement, "false"); return; } node = this._dataAdapter.getNodeByItem(args.itemData); if (node.internalFields.key === selectedIndex[0]) { $itemElement.addClass(this._selectedItemClass()); this._setAriaSelected($itemElement, "true"); } else { this._setAriaSelected($itemElement, "false"); } }, _isItemSelectable: function _isItemSelectable(item) { return item.selectable !== false; }, _renderSeparator: function _renderSeparator(node, index, $itemsContainer) { if (node.beginGroup && index > 0) { this._needSeparate = true; } if (node.visible !== false && this._needSeparate) { if (index > 0) { $("<li>").appendTo($itemsContainer).addClass(DX_MENU_SEPARATOR_CLASS); } this._needSeparate = false; } }, _itemClickHandler: function _itemClickHandler(e) { if (e._skipHandling) return; var itemClickActionHandler = this._createAction(this._updateSubmenuVisibilityOnClick.bind(this)); this._itemDXEventHandler(e, "onItemClick", {}, { afterExecute: itemClickActionHandler.bind(this) }); e._skipHandling = true; }, _updateSubmenuVisibilityOnClick: function _updateSubmenuVisibilityOnClick(actionArgs) { this._updateSelectedItemOnClick(actionArgs); if (this._getShowSubmenuMode() === "onClick") { this._addExpandedClass(actionArgs.args[0].itemElement); } }, _updateSelectedItemOnClick: function _updateSelectedItemOnClick(actionArgs) { var args = actionArgs.args ? actionArgs.args[0] : actionArgs, selectedItemKey; if (!this._isItemSelectionAllowed(args.itemData)) { return; } selectedItemKey = this._dataAdapter.getSelectedNodesKeys(); var selectedNode = selectedItemKey.length && this._dataAdapter.getNodeByKey(selectedItemKey[0]); if (selectedNode) { this._toggleItemSelection(selectedNode, false); } if (!selectedNode || selectedNode.internalFields.item !== args.itemData) { this.selectItem(args.itemData); } else { this._fireSelectionChangeEvent(null, this.option("selectedItem")); this._setOptionSilent("selectedItem", null); } }, _isItemSelectionAllowed: function _isItemSelectionAllowed(item) { var isSelectionByClickEnabled = this._isSelectionEnabled() && this.option("selectByClick"); return !this._isContainerEmpty() && isSelectionByClickEnabled && this._isItemSelectable(item) && !this._itemsGetter(item); }, _isContainerEmpty: function _isContainerEmpty() { return this._itemContainer().is(':empty'); }, _syncSelectionOptions: commonUtils.noop, _optionChanged: function _optionChanged(args) { if (this._cancelOptionChange === args.name) { return; } switch (args.name) { case "showSubmenuMode": break; case "selectedItem": var itemData = args.value, node = this._dataAdapter.getNodeByItem(itemData), selectedKey = this._dataAdapter.getSelectedNodesKeys()[0]; if (node && node.internalFields.key !== selectedKey) { if (node.selectable === false) break; if (selectedKey) { this._toggleItemSelection(this._dataAdapter.getNodeByKey(selectedKey), false); } this._toggleItemSelection(node, true); this._updateSelectedItems(); } break; case "cssClass": case "position": case "selectByClick": case "animation": case "useInkRipple": this._invalidate(); break; default: this.callBase(args); } }, _toggleItemSelection: function _toggleItemSelection(node, value) { var itemElement = this._getElementByItem(node.internalFields.item); itemElement && $(itemElement).toggleClass(DX_MENU_SELECTED_ITEM_CLASS); this._dataAdapter.toggleSelection(node.internalFields.key, value); }, _getElementByItem: function _getElementByItem(itemData) { var that = this, result; each(this._itemElements(), function (_, itemElement) { if ($(itemElement).data(that._itemDataKey()) !== itemData) { return true; } result = itemElement; return false; }); return result; }, _updateSelectedItems: function _updateSelectedItems(oldSelection, newSelection) { if (oldSelection || newSelection) { this._updateSelection(newSelection, oldSelection); this._fireSelectionChangeEvent(newSelection, oldSelection); } }, _fireSelectionChangeEvent: function _fireSelectionChangeEvent(addedSelection, removedSelection) { this._createActionByOption("onSelectionChanged", { excludeValidators: ["disabled", "readOnly"] })({ addedItems: [addedSelection], removedItems: [removedSelection] }); }, /** * @name dxMenuBaseMethods.selectItem * @publicName selectItem(itemElement) * @param1 itemElement:Node */ selectItem: function selectItem(itemElement) { var itemData = itemElement.nodeType ? this._getItemData(itemElement) : itemElement, node = this._dataAdapter.getNodeByItem(itemData), selectedKey = this._dataAdapter.getSelectedNodesKeys()[0], selectedItem = this.option("selectedItem"); if (node.internalFields.key !== selectedKey) { if (selectedKey) { this._toggleItemSelection(this._dataAdapter.getNodeByKey(selectedKey), false); } this._toggleItemSelection(node, true); this._updateSelectedItems(selectedItem, itemData); this._setOptionSilent("selectedItem", itemData); } }, /** * @name dxMenuBaseMethods.unselectItem * @publicName unselectItem(itemElement) * @param1 itemElement:Node */ unselectItem: function unselectItem(itemElement) { var itemData = itemElement.nodeType ? this._getItemData(itemElement) : itemElement, node = this._dataAdapter.getNodeByItem(itemData), selectedItem = this.option("selectedItem"); if (node.internalFields.selected) { this._toggleItemSelection(node, false); this._updateSelectedItems(selectedItem, null); this._setOptionSilent("selectedItem", null); } } }); module.exports = MenuBase;