UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,497 lines (1,300 loc) 125 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides control sap.m.ViewSettingsDialog. sap.ui.define([ './library', 'sap/ui/core/Control', 'sap/ui/core/Element', 'sap/ui/core/IconPool', 'sap/ui/core/Lib', './Toolbar', './CheckBox', './SearchField', './List', './StandardListItem', './Dialog', './Button', './ToggleButton', './Title', './NavContainer', './Bar', './SegmentedButton', './Page', './ViewSettingsItem', 'sap/ui/base/ManagedObject', 'sap/ui/base/ManagedObjectObserver', 'sap/ui/base/EventProvider', 'sap/ui/Device', 'sap/ui/core/InvisibleText', './ViewSettingsDialogRenderer', "sap/m/GroupHeaderListItem", "sap/ui/base/Object", "sap/ui/core/StaticArea", "sap/base/Log", "sap/ui/core/library", "sap/ui/thirdparty/jquery", // jQuery Plugin "firstFocusableDomRef" "sap/ui/dom/jquery/Focusable", "sap/ui/core/InvisibleMessage" ], function( library, Control, Element, IconPool, Library, Toolbar, CheckBox, SearchField, List, StandardListItem, Dialog, Button, ToggleButton, Title, NavContainer, Bar, SegmentedButton, Page, ViewSettingsItem, ManagedObject, ManagedObjectObserver, EventProvider, Device, InvisibleText, ViewSettingsDialogRenderer, GroupHeaderListItem, BaseObject, StaticArea, Log, coreLibrary, jQuery, jQueryFocusable, InvisibleMessage ) { "use strict"; // shortcut for sap.m.ListMode var ListMode = library.ListMode; // shortcut for sap.m.ListType var ListType = library.ListType; // shortcut for sap.m.StringFilterOperator var StringFilterOperator = library.StringFilterOperator; // shortcut for sap.m.TitleAlignment var TitleAlignment = library.TitleAlignment; // shortcut for sap.m.ButtonType var ButtonType = library.ButtonType; // shortcut for sap.ui.core.TitleLevel var TitleLevel = coreLibrary.TitleLevel; var LIST_ITEM_SUFFIX = "-list-item"; var InvisibleMessageMode = coreLibrary.InvisibleMessageMode; /** * Constructor for a new <code>ViewSettingsDialog</code>. * * @param {string} [sId] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * * @class * Helps the user to sort, filter, or group data within a (master) {@link sap.m.List} or a * {@link sap.m.Table}. The dialog is triggered by icon buttons in the table toolbar. Each * button shows a dropdown list icon. * * <h3>Overview</h3> * * The <code>ViewSettingsDialog</code> is a composite control, * consisting of a modal {@link sap.m.Popover} and several internal lists. * There are three different tabs (Sort, Group, Filter) in the dialog that can be * activated by filling the respective associations. If only one association is * filled, the other tabs are automatically hidden. The selected options can be used * to create sorters and filters for the table. * * <b>Note:</b> If the app does not offer all three sorting, filtering, and grouping * operations, but only one of these (such as sort), we recommend placing the * icon button directly in the toolbar. Do not place sort, filter, or group buttons in * the footer toolbar if they refer to a table. Place group, sort, and filter buttons * in the footer toolbar if they refer to a master list. * * <b>Note:</b> If <code>ViewSettingsDialog</code> is used without custom tabs or custom items * in any of its aggregations, then Reset button is enabled if the user selects any Filters or * presetFilters or changes any of the Sort by, Sort order, Group by, or Group order values. * When <code>ViewSettingsDialog</code> is used with custom tabs or custom items * in any of its aggregations (sortItems, groupItems, filterItems or presetFilterItems), * the Reset button is always enabled, because there is no way to determine * the initial state of the custom tabs and compare it to their current state in order to * determine the enable/disable state of the Reset button. * * <h3>Usage</h3> * * <i>When to use?</i> * <ul><li>If you need to allow the user to sort line items in a manageable list or * table (up to 20 columns)</li> * <li>If you need to offer custom filter settings in a manageable list or table * (up to 20 columns)</li> * <li>If you need to allow the user to group line items in a manageable list or * table (up to 20 columns)</li></ul> * * <i>When not to use?</i> * <ul><li>If you have complex tables (more than 20 columns)</li> * <li>If you need to rearrange columns within your table (use the * {@link sap.m.TablePersoDialog} instead)</li> * <li>If you need very specific sort, filter, or column sorting options within * complex tables (use the {@link sap.m.P13nDialog} instead)</li></ul> * * <h3>Responsive behavior</h3> * * The popover dialog appears as a modal window on desktop and tablet screen sizes, * but full screen on smartphones. * * @extends sap.ui.core.Control * * @author SAP SE * @version 1.146.0 * * @constructor * @public * @since 1.16 * @alias sap.m.ViewSettingsDialog * @see {@link fiori:https://experience.sap.com/fiori-design-web/view-settings-dialog/ View Settings Dialog} */ var ViewSettingsDialog = Control.extend("sap.m.ViewSettingsDialog", /** @lends sap.m.ViewSettingsDialog.prototype */ { metadata : { library : "sap.m", properties : { /** * Defines the title of the dialog. If not set and there is only one active tab, the dialog uses the default "View" or "Sort", "Group", "Filter" respectively. */ title : {type : "string", group : "Behavior", defaultValue : null}, /** * Determines whether the sort order is descending or ascending (default). */ sortDescending : {type : "boolean", group : "Behavior", defaultValue : false}, /** * Determines whether the group order is descending or ascending (default). */ groupDescending : {type : "boolean", group : "Behavior", defaultValue : false}, /** * Provides a string filter operator which is used when the user searches items in filter details page. * Possible operators are: <code>AnyWordStartsWith</code>, <code>Contains</code>, <code>StartsWith</code>, <code>Equals</code>. * This property will be ignored if a custom callback is provided through <code>setFilterSearchCallback</code> method. * @since 1.42 */ filterSearchOperator: {type: "sap.m.StringFilterOperator", group: "Behavior", defaultValue: StringFilterOperator.StartsWith }, /** * Specifies the Title alignment (theme specific). * If set to <code>TitleAlignment.None</code>, the automatic title alignment depending on the theme settings will be disabled. * If set to <code>TitleAlignment.Auto</code>, the Title will be aligned as it is set in the theme (if not set, the default value is <code>center</code>); * Other possible values are <code>TitleAlignment.Start</code> (left or right depending on LTR/RTL), and <code>TitleAlignment.Center</code> (centered) * @since 1.72 * @public */ titleAlignment : {type : "sap.m.TitleAlignment", group : "Misc", defaultValue : TitleAlignment.Auto} }, aggregations : { /** * The list of items with key and value that can be sorted over (for example, a list of columns for a table). * @since 1.16 */ sortItems : {type : "sap.m.ViewSettingsItem", multiple : true, singularName : "sortItem", bindable : "bindable"}, /** * The list of items with key and value that can be grouped on (for example, a list of columns for a table). * @since 1.16 */ groupItems : {type : "sap.m.ViewSettingsItem", multiple : true, singularName : "groupItem", bindable : "bindable"}, /** * The list of items with key and value that can be filtered on (for example, a list of columns for a table). A filterItem is associated with one or more detail filters. * * <b>Note:</b> It is recommended to use the <code>sap.m.ViewSettingsFilterItem</code> as it fits best at the filter page. * @since 1.16 */ filterItems : {type : "sap.m.ViewSettingsItem", multiple : true, singularName : "filterItem", bindable : "bindable"}, /** * The list of preset filter items with key and value that allows the selection of more complex or custom filters. * These entries are displayed at the top of the filter tab. * @since 1.16 */ presetFilterItems : {type : "sap.m.ViewSettingsItem", multiple : true, singularName : "presetFilterItem", bindable : "bindable"}, /** * The list of all the custom tabs. * @since 1.30 */ customTabs: {type: "sap.m.ViewSettingsCustomTab", multiple: true, singularName: "customTab", bindable : "bindable"} }, associations : { /** * The sort item that is selected. It can be set by either passing a key or the item itself to the function setSelectedSortItem. */ selectedSortItem : {type : "sap.m.ViewSettingsItem", multiple : false}, /** * The group item that is selected. It can be set by either passing a key or the item itself to the function setSelectedGroupItem. * By default 'None' is selected. You can restore back to 'None' by setting this association to empty value. */ selectedGroupItem : {type : "sap.m.ViewSettingsItem", multiple : false}, /** * The preset filter item that is selected. It can be set by either passing a key or the item itself to the function setSelectedPresetFilterItem. Note that either a preset filter OR multiple detail filters can be active at the same time. */ selectedPresetFilterItem : {type : "sap.m.ViewSettingsItem", multiple : false} }, events : { /** * Indicates that the user has pressed the OK button and the selected sort, group, and filter settings should be applied to the data on this page. * </br></br><b>Note:</b> Custom tabs are not converted to event parameters automatically. For custom tabs, you have to read the state of your controls inside the callback of this event. */ confirm : { parameters : { /** * The selected sort item. */ sortItem : {type : "sap.m.ViewSettingsItem"}, /** * The selected sort order (true = descending, false = ascending). */ sortDescending : {type : "boolean"}, /** * The selected group item. */ groupItem : {type : "sap.m.ViewSettingsItem"}, /** * The selected group order (true = descending, false = ascending). */ groupDescending : {type : "boolean"}, /** * The selected preset filter item. */ presetFilterItem : {type : "sap.m.ViewSettingsItem"}, /** * The selected filters in an array of ViewSettingsItem. */ filterItems : {type : "sap.m.ViewSettingsItem[]"}, /** * The selected filter items in an object notation format: { key: boolean }. If a custom control filter was displayed (for example, the user clicked on the filter item), the value for its key is set to true to indicate that there has been an interaction with the control. * @deprecated as of version 1.42, replaced by <code>filterCompoundKeys</code> event */ filterKeys : {type : "object", deprecated: true}, /** * The selected filter items in an object notation format: { parentKey: { key: boolean, key2: boolean, ... }, ...}. If a custom control filter was displayed (for example, the user clicked on the filter item), the value for its key is set to true to indicate that there has been an interaction with the control. * @since 1.42 */ filterCompoundKeys : {type : "object"}, /** * The selected filter items in a string format to display in the control's header bar in format "Filtered by: key (subkey1, subkey2, subkey3)". */ filterString : {type : "string"} } }, /** * Called when the Cancel button is pressed. It can be used to set the state of custom filter controls. */ cancel : {}, /** * Called when the filters are being reset. */ resetFilters : {}, /** * Called when the Reset button is pressed. It can be used to set the state of custom tabs. */ reset : {}, /** * Fired when the filter detail page is opened. */ filterDetailPageOpened: { parameters: { /** * The filter item for which the details are opened. */ parentFilterItem: {type: "sap.m.ViewSettingsFilterItem"} } }, /** * Fired before the dialog is closed. * This event can be prevented which effectively prevents the dialog from closing. * @since 1.132 */ beforeClose : { allowPreventDefault : true } } }, renderer: ViewSettingsDialogRenderer }); /* =========================================================== */ /* begin: API methods */ /* =========================================================== */ ViewSettingsDialog.prototype.init = function() { var sId = this.getId(); this._rb = Library.getResourceBundleFor("sap.m"); this._sDialogWidth = "350px"; this._sDialogHeight = "434px"; /* this control does not have a renderer, so we need to take care of adding it to the ui tree manually */ this._bAppendedToUIArea = false; this._showSubHeader = false; this._filterDetailList = undefined; this._vContentPage = -1; this._oContentItem = null; this._oPreviousState = {}; this._sCustomTabsButtonsIdPrefix = '-custom-button-'; this._sTitleLabelId = sId + "-title"; this._sFilterDetailTitleLabelId = sId + "-detailtitle"; this._oFiltersSelectedOnly = {}; this._oKeylessFilters = {}; this._oInvisibleMessage = null; /* setup a name map between the sortItems aggregation and an sap.m.List with items the list itself will not be created right now */ this._aggregationToListItems("sortItems", { text: { listProp: "title" }, selected: { }, wrapping: { } }, { tooltip: {} },{ type : ListType.Inactive }, { mode : ListMode.SingleSelectLeft, includeItemInSelection : true, selectionChange : function(oEvent) { var oListItem = oEvent.getParameter('listItem'), aItems = this.getSortItems(), oVSItemToSelect = this._getVSItem(oListItem), i; oVSItemToSelect.setProperty('selected', oEvent.getParameter('selected'), true, false); this.setAssociation('selectedSortItem', oVSItemToSelect, true); for (i = 0; i < aItems.length; i++) { if (oVSItemToSelect !== aItems[i]) { aItems[i].setProperty('selected', false, true, false); } } // enable/disable reset button if necessary this._checkResetStatus(); }.bind(this) }); }; ViewSettingsDialog.prototype.exit = function() { // helper variables this._rb = null; this._sDialogWidth = null; this._sDialogHeight = null; this._bAppendedToUIArea = null; this._showSubHeader = null; this._vContentPage = null; this._oContentItem = null; this._oPreviousState = null; this._sortContent = null; this._groupContent = null; this._filterContent = null; this._sCustomTabsButtonsIdPrefix = null; this._fnFilterSearchCallback = null; this._oKeylessFilters = null; this._oInvisibleMessage = null; // sap.ui.core.Popup removes its content on close()/destroy() automatically from the static UIArea, // but only if it added it there itself. As we did that, we have to remove it also on our own if ( this._bAppendedToUIArea && this._dialog ) { var oStatic = StaticArea.getUIArea(); oStatic.removeContent(this._dialog, true); } // controls that are internally managed and may or may not be assigned to an // aggregation (have to be destroyed manually to be sure) // dialog if (this._dialog) { this._dialog.destroy(); this._dialog = null; } if (this._navContainer) { this._navContainer.destroy(); this._navContainer = null; } if (this._titleLabel) { this._titleLabel.destroy(); this._titleLabel = null; } // page1 (sort/group/filter) if (this._page1) { this._page1.destroy(); this._page1 = null; } if (this._header) { this._header.destroy(); this._header = null; } if (this._resetButton) { this._resetButton.destroy(); this._resetButton = null; } if (this._subHeader) { this._subHeader.destroy(); this._subHeader = null; } if (this._segmentedButton) { this._segmentedButton.destroy(); this._segmentedButton = null; } if (this._sortButton) { this._sortButton.destroy(); this._sortButton = null; } if (this._groupButton) { this._groupButton.destroy(); this._groupButton = null; } if (this._filterButton) { this._filterButton.destroy(); this._filterButton = null; } if (this._sortList) { this._sortList.destroy(); this._sortList = null; } if (this._sortOrderList) { this._sortOrderList.destroy(); this._sortOrderList = null; } if (this._oGroupingNoneItem) { this._oGroupingNoneItem.destroy(); this._oGroupingNoneItem = null; } if (this._groupList) { this._groupList.destroy(); this._groupList = null; } if (this._groupOrderList) { this._groupOrderList.destroy(); this._groupOrderList = null; } if (this._presetFilterList) { this._presetFilterList.destroy(); this._presetFilterList = null; } if (this._filterList) { this._filterList.destroy(); this._filterList = null; } // page2 (filter details) if (this._page2) { this._page2.destroy(); this._page2 = null; } if (this._detailTitleLabel) { this._detailTitleLabel.destroy(); this._detailTitleLabel = null; } if (this._filterDetailList) { this._filterDetailList.destroy(); this._filterDetailList = null; } if (this._oStringFilter) { this._oStringFilter = null; } if (this._oSelectedFilterKeys) { this._oSelectedFilterKeys = null; } }; ViewSettingsDialog.prototype._aggregationToListItems = function(sAggregationName, oItemPropertyMap, oItemAggregationMap, oListItemInitials, oListOptions) { var sType = this._getListType(sAggregationName), sListName = "_" + sType + "List"; if (!this.mToList) { this.mToList = {}; } this.mToList[sType] = { "itemPropertyMap": oItemPropertyMap, "itemAggregationMap": oItemAggregationMap, "listItemOptions": oListItemInitials, "listOptions": oListOptions, "listName": sListName }; }; ViewSettingsDialog.prototype._getListType = function(sAggregationName) { return sAggregationName.replace('Items', ''); }; ViewSettingsDialog.prototype._createList = function(sType) { var sListId = this.getId() + "-" + sType + "list", oList = new List(sListId, this.mToList[sType].listOptions), oGHI = this._createGroupHeaderItem(sType); oList.addItemGroup(undefined, oGHI); this[this.mToList[sType].listName] = oList; return oList; }; ViewSettingsDialog.prototype._createGroupHeaderItem = function(sListType) { return new GroupHeaderListItem({ title: this._rb.getText("VIEWSETTINGS_" + sListType.toUpperCase() + "_OBJECT") }); }; ViewSettingsDialog.prototype._getList = function(sType) { if (!this.mToList || !this.mToList[sType]) { return; } return this[this.mToList[sType].listName]; }; ViewSettingsDialog.prototype._createListItem = function(sType, oVSItem) { var oOptions = this.mToList[sType].listItemOptions, mItemPropertyMap = this.mToList[sType].itemPropertyMap, mItemAggregationMap = this.mToList[sType].itemAggregationMap, sListProp, oCreatedListItem; // Pass the properties for (var sProperty in mItemPropertyMap) { if (mItemPropertyMap.hasOwnProperty(sProperty)) { sListProp = mItemPropertyMap[sProperty].listProp || sProperty; oOptions[sListProp] = this._createListItemPropertyValue(sType, sProperty, oVSItem); } } // Pass the aggregations for (var sAggregationName in mItemAggregationMap) { if (mItemAggregationMap.hasOwnProperty(sAggregationName)) { oOptions[sAggregationName] = oVSItem.getAggregation(sAggregationName); } } oCreatedListItem = new StandardListItem(oOptions).data('item', oVSItem); return oCreatedListItem; }; ViewSettingsDialog.prototype._createListItemPropertyValue = function(sType, sPropertyName, oVSItem) { var vVal = ManagedObject.escapeSettingsValue(oVSItem.getMetadata().getAllProperties()[sPropertyName].get(oVSItem)), fn = this.mToList[sType].itemPropertyMap[sPropertyName].fn; return fn ? fn(vVal) : vVal; }; ViewSettingsDialog.prototype._getListItem = function(sType, oVSItem) { var aListItems = this._getList(sType).getItems().filter(function(oItem) { return oItem.data('item') === oVSItem; }); return aListItems.length ? aListItems[0] : null; }; ViewSettingsDialog.prototype._getVSItem = function(oListItem) { return oListItem.data('item'); }; /** * Overwrites the aggregation setter in order to have ID validation logic as some strings * are reserved for the predefined tabs. * * @override * @public * @param {sap.m.ViewSettingsCustomTab} oCustomTab The custom tab to be added * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.addCustomTab = function (oCustomTab) { var sId = oCustomTab.getId(); if (sId === 'sort' || sId === 'filter' || sId === 'group') { throw 'Id "' + sId + '" is reserved and cannot be used as custom tab id.'; } this.addAggregation('customTabs', oCustomTab); return this; }; ViewSettingsDialog.prototype.invalidate = function() { // Invalidate call on ViewSettingsDialog is directly forwarded to the sap.m.Dialog // since the ViewSettingsDialog has no renderer. // CSN #80686/2014: only invalidate inner dialog if call does not come from inside // BCP #1670394376 if (this._dialog && (!arguments[0] || arguments[0] && arguments[0].getId() !== this.getId() + "-dialog")) { this._dialog.invalidate(arguments); } }; /** * Forward method to the inner dialog method: addStyleClass. * @public * @override * @param {string} sStyleClass CSS class name to add * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.addStyleClass = function () { var oDialog = this._getDialog(); oDialog.addStyleClass.apply(oDialog, arguments); return this; }; /** * Forward method to the inner dialog method: removeStyleClass. * @public * @override * @param {string} sStyleClass CSS class name to remove * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.removeStyleClass = function () { var oDialog = this._getDialog(); oDialog.removeStyleClass.apply(oDialog, arguments); return this; }; /** * Forward method to the inner dialog method: toggleStyleClass. * @public * @override * @param {string} sStyleClass CSS class name to add or remove * @param {boolean} [bAdd] Whether style class should be added (or removed); when this parameter is not given, the given style class will be toggled (removed, if present, and added if not present) * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.toggleStyleClass = function () { var oDialog = this._getDialog(); oDialog.toggleStyleClass.apply(oDialog, arguments); return this; }; /** * Forward method to the inner dialog method: hasStyleClass. * @public * @override * @returns {boolean} true if the class is set, false otherwise */ ViewSettingsDialog.prototype.hasStyleClass = function () { var oDialog = this._getDialog(); return oDialog.hasStyleClass.apply(oDialog, arguments); }; /** * Forward method to the inner dialog method: getDomRef. * @public * @override * @returns {Element|null} The Element's DOM Element, sub DOM Element or <code>null</code> */ ViewSettingsDialog.prototype.getDomRef = function () { // this is also called on destroy to remove the DOM element, therefore we directly check the reference instead of the internal getter if (this._dialog) { return this._dialog.getDomRef.apply(this._dialog, arguments); } else { return null; } }; /** * Sets the title of the internal dialog. * * @override * @public * @param {string} sTitle The title text for the dialog * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.setTitle = function(sTitle) { this._getTitleLabel().setText(sTitle); this.setProperty("title", sTitle); return this; }; ViewSettingsDialog.prototype.setTitleAlignment = function (sAlignment) { this.setProperty("titleAlignment", sAlignment); if (this._page1) { this._page1.setTitleAlignment(sAlignment); } if (this._page2) { this._page2.setTitleAlignment(sAlignment); } return this; }; /** * Override the method in order to attach some event handlers * @override * @param {string} sAggregationName Name of the added aggregation * @param {object} oObject Instance that is going to be added * @param {boolean} bSuppressInvalidate Flag indicating whether invalidation should be supressed * @returns {object} This instance for chaining */ ViewSettingsDialog.prototype.addAggregation = function (sAggregationName, oObject, bSuppressInvalidate) { Control.prototype.addAggregation.apply(this, arguments); var sType = this._getListType(sAggregationName); if (this.mToList[sType]) { var oListItem = this._createListItem(sType, oObject); var oList = this._getList(sType); if (!oList) { oList = this._createList(sType); } else if (!oList.getItems().length) { oList.addItemGroup(undefined, this._createGroupHeaderItem(sType)); } oList.addItem(oListItem); this._attachItemPropertyChange(sType, oObject); } else { this._attachItemEventHandlers(sAggregationName, oObject); } if (sAggregationName === "filterItems") { this._observeItem(oObject); } return this; }; /** * Override the method in order to attach some event handlers * @override * @param {string} sAggregationName Name of the added aggregation * @param {object} oObject Instance that is going to be added * @param {int} iIndex the <code>0</code>-based index the managed object should be inserted * @param {boolean} bSuppressInvalidate Flag indicating whether invalidation should be supressed * @returns {sap.ui.base.ManagedObject} Returns <code>this</code> to allow method chaining */ ViewSettingsDialog.prototype.insertAggregation = function(sAggregationName, oObject, iIndex, bSuppressInvalidate) { Control.prototype.insertAggregation.apply(this, arguments); var sType = this._getListType(sAggregationName); if (this.mToList[sType]) { var oListItem = this._createListItem(sType, oObject); var oList = this._getList(sType); if (!oList) { oList = this._createList(sType); oList.insertItem(oListItem, iIndex); } else if (!oList.getItems().length) { oList.addItemGroup(undefined, this._createGroupHeaderItem(sType)); oList.insertItem(oListItem, iIndex + 1); } else { oList.insertItem(oListItem, iIndex); } this._attachItemPropertyChange(sType, oObject); } else { this._attachItemEventHandlers(sAggregationName, oObject); } if (sAggregationName === "filterItems") { this._observeItem(oObject); } return this; }; ViewSettingsDialog.prototype.removeAggregation = function(sAggregationName, vObject, bSuppressInvalidate) { restoreCustomTabContentAggregation.call(this, sAggregationName, vObject); var vRemovedObject = Control.prototype.removeAggregation.apply(this, arguments); var sType = this._getListType(sAggregationName); if (this.mToList[sType]) { var oListItem = this._getListItem(sType, vRemovedObject); var oList = this._getList(sType); var oRemovedListItem = oList.removeItem(oListItem); oRemovedListItem.destroy(); this._detachItemPropertyChange(vRemovedObject); } if (sAggregationName === "filterItems") { this._unobserveItem(vRemovedObject); if (!this.getFilterItems().length) { this._disconnectAndDestroyFilterItemsObserver(); } } return vRemovedObject; }; ViewSettingsDialog.prototype.removeAllAggregation = function(sAggregationName, bSuppressInvalidate) { // custom tabs aggregation needs special handling - make sure it happens restoreCustomTabContentAggregation.call(this); var vRemovedObjects = Control.prototype.removeAllAggregation.apply(this, arguments); var sType = this._getListType(sAggregationName); if (this.mToList[sType]) { var oList = this._getList(sType); if (oList) { //we may not have any internal lists (e.g. no sortItems for this VSD instance) var oRemovedListItems = oList.removeAllItems(); oRemovedListItems.forEach(function(oItem) { oItem.destroy(); }); } vRemovedObjects.forEach(function(oItem) { this._detachItemPropertyChange(oItem); }, this); } if (sAggregationName === "filterItems") { this._disconnectAndDestroyFilterItemsObserver(); } return vRemovedObjects; }; ViewSettingsDialog.prototype.destroyAggregation = function(sAggregationName, bSuppressInvalidate) { restoreCustomTabContentAggregation.call(this); Control.prototype.destroyAggregation.apply(this, arguments); var sType = this._getListType(sAggregationName); if (this.mToList[sType]) { var oList = this._getList(sType); if (oList) { oList.destroyItems(); } } if (sAggregationName === "filterItems") { this._disconnectAndDestroyFilterItemsObserver(); } return this; }; ViewSettingsDialog.prototype._detachItemPropertyChange = function(oVSItem) { delete EventProvider.getEventList(oVSItem)["itemPropertyChanged"]; }; ViewSettingsDialog.prototype._attachItemPropertyChange = function(sType, oVSItem) { oVSItem.attachEvent('itemPropertyChanged', function fnHandleItemPropertyChanged(oEvent) { var oListItem, sProp, sListProp, vVal, fn, vListPropVal; oListItem = this._getListItem(sType, oVSItem); sProp = oEvent.getParameter('propertyKey'); if (!this.mToList[sType].itemPropertyMap[sProp]) { return; } sListProp = this.mToList[sType].itemPropertyMap[sProp].listProp || sProp; vVal = oEvent.getParameter('propertyValue'); fn = this.mToList[sType].itemPropertyMap[sProp].fn; vListPropVal = fn ? fn(vVal) : vVal; oListItem.getMetadata().getAllProperties()[sListProp].set(oListItem, vListPropVal); }, this); }; /** * Returns the ManagedObjectObserver for the filterItems * * @returns {sap.ui.base.ManagedObjectObserver} the filterItems observer object * @private */ ViewSettingsDialog.prototype._getFilterItemsObserver = function () { if (!this._oFilterItemsObserver) { this._oFilterItemsObserver = new ManagedObjectObserver(function() { if (this._oSelectedFilterKeys) { this.setSelectedFilterCompoundKeys(this._oSelectedFilterKeys); } }.bind(this)); } return this._oFilterItemsObserver; }; /** * Observes the items aggregation of the passed filterItem * * @param {sap.m.ViewSettingsFilterItem} oFilterItem the filterItem, which aggregation will be observed * @private */ ViewSettingsDialog.prototype._observeItem = function (oFilterItem) { this._getFilterItemsObserver().observe(oFilterItem, { aggregations: ["items"] }); }; /** * Unobserves the items aggregation of the passed filterItem * * @param {sap.m.ViewSettingsFilterItem} oFilterItem the filterItem, which aggregation will be unobserved * @private */ ViewSettingsDialog.prototype._unobserveItem = function (oFilterItem) { this._getFilterItemsObserver().unobserve(oFilterItem, { aggregations: ["items"] }); }; /** * Disconnects and destroys the ManagedObjectObserver observing the used filterItem * * @private */ ViewSettingsDialog.prototype._disconnectAndDestroyFilterItemsObserver = function () { if (this._oFilterItemsObserver) { this._oFilterItemsObserver.disconnect(); this._oFilterItemsObserver.destroy(); this._oFilterItemsObserver = null; } }; /** * Attaches event handlers responsible for propagating * item property changes as well as filter detail items' change. * @param {string} sAggregationName The name of the aggregation * @param {Object} oObject * @returns {object} This instance for chaining */ ViewSettingsDialog.prototype._attachItemEventHandlers = function(sAggregationName, oObject) { // perform the following logic only for the items aggregations, except custom tabs if (sAggregationName !== 'groupItems' && sAggregationName !== 'filterItems') { return this; } var sType = sAggregationName.replace('Items', ''); // extract "filter"/"group" sType = sType.charAt(0).toUpperCase() + sType.slice(1); // capitalize // Attach 'itemPropertyChanged' handler, that will re-initiate (specific) dialog content oObject.attachEvent('itemPropertyChanged', function (sAggregationName, oEvent) { /* If the changed item was a 'sap.m.ViewSettingsItem' * then threat it differently as filter detail item. * */ if (sAggregationName === 'filterItems' && oEvent.getParameter('changedItem').getParent().getMetadata().getName() === 'sap.m.ViewSettingsFilterItem') { // handle the select differently if (oEvent.getParameter('propertyKey') !== 'selected') { // if on filter details page for a concrete filter item if (this._vContentPage === 3 && this._oContentItem) { this._setFilterDetailTitle(this._oContentItem); this._initFilterDetailItems(this._oContentItem); } } else { // Change only the "select" property on the concrete item (instead calling _initFilterDetailItems() all over) to avoid re-rendering // ToDo: make this optimization for all properties if (this._filterDetailList) { var aItems = this._filterDetailList.getItems(); aItems.forEach(function (oItem) { if (oItem.data('item').getId() === oEvent.getParameter('changedItem').getId()) { oItem.setSelected(oEvent.getParameter('propertyValue')); } }); this._updateSelectAllCheckBoxState(); } } this._updateFilterCounters(); //selected item has changed, so counters must be updated } else { // call _initFilterContent and _initFilterItems methods, where "Filter" might be also "Group" if (typeof this['_init' + sType + 'Content'] === 'function') { this['_init' + sType + 'Content'](); } if (typeof this['_init' + sType + 'Items'] === 'function') { if (oEvent.getParameter("propertyKey") === "selected" && oEvent.getParameter("propertyValue") === true) { this.setAssociation("selectedGroupItem", oEvent.getParameter("changedItem"), true); } this['_init' + sType + 'Items'](); } } }.bind(this, sAggregationName)); // Attach 'filterDetailItemsAggregationChange' handler, that will re-initiate (specific) dialog content oObject.attachEvent('filterDetailItemsAggregationChange', function (oEvent) { if (this._vContentPage === 3 && this._oContentItem) { this._setFilterDetailTitle(this._oContentItem); this._initFilterDetailItems(this._oContentItem); } }.bind(this)); return this; }; /** * Set header title for the filter detail page. * @param {object} oItem Item that will serve as a title * @private */ ViewSettingsDialog.prototype._setFilterDetailTitle = function (oItem) { var sText = this._rb.getText("VIEWSETTINGS_TITLE_FILTERBY") + " " + oItem.getText(); this._getDetailTitleLabel().setText(sText); // Update the dialog's title as well, so that the new detail title can be read out by the screen reader this._toggleDialogTitle(this._sFilterDetailTitleLabelId); }; /** * Adds a reference to a specific title in the dialog's <code>aria-labelledby</code> attribute. * * @param {string} sNewTitleId Title's ID * @returns {this} Reference to <code>this</code> for method chaining * @private */ ViewSettingsDialog.prototype._toggleDialogTitle = function (sNewTitleId) { var oDialog = this._getDialog(), aAriaLabelledByIds = oDialog.getAriaLabelledBy(), // Title options are in an array, for ease of extending this in the future. aTitleOptions = [ this._sTitleLabelId, this._sFilterDetailTitleLabelId ]; if (aTitleOptions.indexOf(sNewTitleId) === -1 && aAriaLabelledByIds.indexOf(sNewTitleId) > -1) { // Don't do anything if the title isn't one of the options, or it's already set in the ariaLabelledBy return this; } for (var sTitleOptionId in aTitleOptions) { // Make sure that only the new title will be in ariaLabelledBy oDialog.removeAriaLabelledBy(aTitleOptions[sTitleOptionId]); } oDialog.addAriaLabelledBy(sNewTitleId); return this; }; /** * Take care to update the internal instances when any of the corresponding aggregation is being updated. * * @override * @param {string} sAggregationName Name of the updated aggregation * @returns {ViewSettingsDialog} this instance for chaining */ ViewSettingsDialog.prototype.updateAggregation = function (sAggregationName) { Control.prototype.updateAggregation.apply(this, arguments); // perform the following logic only for the items aggregations, except custom tabs if (sAggregationName !== 'groupItems' && sAggregationName !== 'filterItems') { return this; } var sType = sAggregationName.replace('Items', ''); // extract "filter"/"group"/"sort" sType = sType.charAt(0).toUpperCase() + sType.slice(1); // capitalize // call _initFilterContent and _initFilterItems methods, where "Filter" might be also "Group" or "Sort" if (typeof this['_init' + sType + 'Content'] === 'function') { this['_init' + sType + 'Content'](); } if (typeof this['_init' + sType + 'Items'] === 'function') { this['_init' + sType + 'Items'](); } }; /** * Adds a sort item and sets the association to reflect the selected state. * * @override * @public * @param {sap.m.ViewSettingsItem} oItem The item to be added to the aggregation * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.addSortItem = function(oItem) { this.addAggregation("sortItems", oItem); if (this.getSelectedSortItem() === oItem.getId() || this.getSelectedSortItem() === oItem.getKey()) { oItem.setSelected(true); } if (oItem.getSelected()) { this.setAssociation("selectedSortItem", oItem, true); } return this; }; /** * Adds a group item and sets the association to reflect the selected state. * * @override * @public * @param {sap.m.ViewSettingsItem} oItem The item to be added to the group items * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.addGroupItem = function(oItem) { this.addAggregation("groupItems", oItem); if (this.getSelectedGroupItem() === oItem.getId() || this.getSelectedGroupItem() === oItem.getKey()) { oItem.setSelected(true); } if (oItem.getSelected()) { this.setAssociation("selectedGroupItem", oItem, true); } return this; }; /** * Adds a preset filter item and sets the association to reflect the selected state. * * @override * @public * @param {sap.m.ViewSettingsItem} oItem The selected item or a string with the key * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.addPresetFilterItem = function(oItem) { this.addAggregation("presetFilterItems", oItem); if (oItem.getSelected()) { this.setSelectedPresetFilterItem(oItem); } return this; }; /** * Sets the selected sort item (either by key, item id or item instance). * * @override * @public * @param {sap.m.ViewSettingsItem|sap.ui.core.ID|string} vItemOrKey The selected item, the item's string key * or the item id * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.setSelectedSortItem = function(vItemOrKey) { var aItems = this.getSortItems(), i = 0, oItem = findViewSettingsItemByKey( vItemOrKey, aItems ); if (!oItem && (typeof vItemOrKey === "string")) { oItem = Element.getElementById(vItemOrKey); } //change selected item only if it is found among the sort items or if there is no selected item if (!oItem || validateViewSettingsItem(oItem)) { // set selected = true for this item & selected = false for all others items for (i = 0; i < aItems.length; i++) { if (!oItem || aItems[i].getId() !== oItem.getId()) { aItems[i].setProperty('selected', false, true); } } if (oItem && oItem.getProperty('selected') !== true) { oItem.setProperty('selected', true, true); } // update the list selection if (this._getDialog().isOpen()) { this._updateListSelection(this._sortList, oItem); } } this.setAssociation("selectedSortItem", oItem || vItemOrKey, true); return this; }; /** * Sets the selected group item (either by key, item id or item instance). * * @override * @public * @param {sap.m.ViewSettingsItem|sap.ui.core.ID|string} vItemOrKey The selected item, the item's string key * or the item id * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.setSelectedGroupItem = function(vItemOrKey) { var aItems = this.getGroupItems(), i = 0, oItem = findViewSettingsItemByKey( vItemOrKey, aItems ); if (!oItem && (typeof vItemOrKey === "string")) { oItem = Element.getElementById(vItemOrKey); } // if no Item is found and the key is empty set the default "None" item as selected // BCP: 1780536754 if (!oItem && !vItemOrKey) { oItem = this._oGroupingNoneItem; this.setAssociation("selectedGroupItem", oItem, true); return this; } //change selected item only if it is found among the group items if (validateViewSettingsItem(oItem)) { // set selected = true for this item & selected = false for all others items for (i = 0; i < aItems.length; i++) { aItems[i].setProperty('selected', false, true); } oItem.setProperty('selected', true, true); // update the list selection if (this._getDialog().isOpen()) { this._updateListSelection(this._groupList, oItem); } } this.setAssociation("selectedGroupItem", oItem || vItemOrKey, true); return this; }; /** * Sets the selected preset filter item. * * @override * @public * @param {sap.m.ViewSettingsItem|sap.ui.core.ID|string|null} vItemOrKey The selected item or the item's key string * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.setSelectedPresetFilterItem = function(vItemOrKey) { var aItems = this.getPresetFilterItems(), i = 0, oItem = findViewSettingsItemByKey( vItemOrKey, aItems, "Could not set selected preset filter item. Item is not found: '" + vItemOrKey + "'" ); //change selected item if it is found among the preset filter items or in case of resetting the value if (validateViewSettingsItem(oItem) || oItem === null) { // set selected = true for this item & selected = false for all others items for (i = 0; i < aItems.length; i++) { aItems[i].setProperty('selected', false, true); } if (oItem !== null) { oItem.setProperty('selected', true, true); } // clear filters (only one mode is allowed, preset filters or filters) this._clearSelectedFilters(); this.setAssociation("selectedPresetFilterItem", oItem, true); } return this; }; /** * Opens the ViewSettingsDialog relative to the parent control. * * @public * @param {string} [sPageId] The ID of the initial page to be opened in the dialog. * The available values are "sort", "group", "filter" or IDs of custom tabs. * * @returns {this} Reference to <code>this</code> for method chaining */ ViewSettingsDialog.prototype.open = function(sPageId) { // add to static UI area manually because we don't have a renderer if (!this.getParent() && !this._bAppendedToUIArea) { var oStatic = StaticArea.getUIArea(); // add as content the Dialog to the Static area and not the ViewSettingsDialog // once the Static area is invalidated, the Dialog will be rendered and not the ViewSettingsDialog which has no renderer // and uses the renderer of the Dialog oStatic.addContent(this._getDialog(), true); this._bAppendedToUIArea = true; } // if there is a default tab and the user has been at filter details view on page2, go back to page1 if (sPageId && this._vContentPage === 3) { setTimeout(this._getNavContainer()["to"].bind(this._getNavContainer(), this._getPage1().getId(), "show"), 0); } // init the dialog content based on the aggregations this._initDialogContent(sPageId); // store the current dialog state to be able to reset it on cancel this._oPreviousState = { sortItem : Element.getElementById(this._getSelectedSortItem()), sortDescending : this.getSortDescending(), groupItem : Element.getElementById(this._getSelectedGroupItem()), groupDescending : this.getGroupDescending(), presetFilterItem : Element.getElementById(this.getSelectedPresetFilterItem()), /** * @deprecated as of version 1.42 */ filterKeys : this.getSelectedFilterKeys(), filterCompoundKeys: this.getSelectedFilterCompoundKeys(), navPage : this._getNavContainer().getCurrentPage(), contentPage : this._vContentPage, contentItem : this._oContentItem }; // store initial dialog state in order to be able to reset it on reset button click if (!this._oInitialState) { this._oInitialState = { sortItem: this._getSelectedSortItem(), sortDescending: this.getSortDescending(), groupItem: this._getSelectedGroupItem(), groupDescending: this.getGroupDescending(), presetFilterItem: this.getSelectedPresetFilterItem() }; } //focus the first focusable item in current page's content if (Device.system.desktop) { this._getDialog().attachEventOnce("afterOpen", this._focusFirstListItem, this); } this._checkResetStatus(); // open dialog this._getDialog().open(); if (!this._oInvisibleMessage) { this._oInvisibleMessage = InvisibleMessage.getInstance(); } return this; }; ViewSettingsDialog.prototype._focusFirstListItem = function() { var oCurrentPage = this._getNavContainer().getCurrentPage(), $firstFocusable; if (oCurrentPage) { $firstFocusable = oCurrentPage.$("cont").firstFocusableDomRef(); if ($firstFocusable) { if (jQuery($firstFocusable).hasClass('sapMListUl')) { var $aListItems = jQuery($firstFocusable).find('.sapMLIB'); $aListItems.length && $aListItems[0].focus(); return; } $firstFocusable.focus(); } } }; /** * Returns the selected filters as an array of ViewSettingsItems. * * It can be used to create matching sorters and filters to apply the selected settings to the data. * @override * @public * @returns {sap.m.ViewSettingsItem[]} An array of selected filter items */ ViewSettingsDialog.prototype.getSelectedFilterItems = function() { var aSelectedFilterItems = [], aFilterItems = this.getFilterItems(), aSubFilterItems, bMultiSelect = true, i = 0, j; for (; i < aFilterItems.length; i++) { if (BaseObject.isObjectA(aFilterItems[i], "sap.m.ViewSettingsCustomItem")) { if (aFilterItems[i].getSelected()) { aSelectedFilterItems.push(aFilterItems[i]); } } else if (BaseObject.isObjectA(aFilterItems[i], "sap.m.ViewSettingsFilterItem")) { aSubFilterItems = aFilterItems[i].getItems(); bMultiSelect = aFilterItems[i].getMultiSelect(); for (j = 0; j < aSubFilterItems.length; j++) { if (aSubFilterItems[j].getSelected()) { aSelectedFilterItems.push(aSubFilterItems[j]); if (!bMultiSelect) { break; // only first item is added to the selection on // single select items } } } } } return aSelectedFilterItems; }; /** * Gets the filter string in format: "filter name (subfilter1 name, subfilter2 * name, ...), ...". * For custom and preset filters it will only add the filter name to the resulting string. * * @public * @returns {string} The selected filter string */ ViewSettingsDialog.prototype.getSelectedFilterString = function() { var sFilterString = "", sSubfilterString, oPresetFilterItem = this.getSelectedPresetFilterItem(), aFilterItems = this.getFilterItems(), aSubFilterItems, bMultiSelect = true, bSelectedFilters, i = 0, j; if (oPresetFilterItem) { // preset filter: add "filter name" sFilterString = this._rb.getText("VIEWSETTINGS_FILTERTEXT").concat(" " + Element.getElementById(oPresetFilterItem).getText()); } else { // standard & custom filters for (; i < aFilterItems.length; i++) { bSelectedFilters = false; if (BaseObject.isObjectA(aFilterItems[i], "sap.m.ViewSettingsCustomItem")) { // custom filter: add "filter name," if (aFilterItems[i].getSelected()) { bSelectedFilters = true; sFilterString += aFilterItems[i].getText() + ", "; } } else if (BaseObject.isObjectA(aFilterItems[i], "sap.m.ViewSettingsFilterItem")) { // standard filter: add "filter name (sub filter 1 name, sub // filter 2 name, ...), " aSubFilterItems = aFilterItems[i].getItems(); bMultiSelect = aFilterItems[i].getMultiSelect(); sSubfilterString = ""; for (j = 0; j < aSubFilterItems.length; j++) { if (aSubFilterItems[