UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

357 lines (290 loc) 12.9 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ './SelectionController', 'sap/m/p13n/modules/xConfigAPI', 'sap/base/Log', 'sap/base/util/merge', 'sap/base/util/deepEqual' ], (BaseController, xConfigAPI, Log, merge, deepEqual) => { "use strict"; /** * Personalization <code>FilterState</code> object type. This object describes the state processed by this controller when accessing it through the {@link sap.m.p13n.Engine Engine}. * * @public * @typedef {Object<string, sap.m.p13n.FilterStateItem[]>} sap.m.p13n.FilterState */ /** * Personalization <code>FilterStateItem</code> object type. This object describes a single filter condition. * * @public * @typedef {object} sap.m.p13n.FilterStateItem * @property {sap.ui.model.FilterOperator} operator The operator of the condition * @property {string[]} values The values of the condition * @property {boolean} [filtered] Defines whether the item is filtered (if a filter state is provided, it's filtered automatically) * */ /** * Constructor for a new <code>FilterController</code>. * * @param {object} mSettings Initial settings for the new controller * @param {sap.ui.core.Control} mSettings.control The control instance that is personalized by this controller * @param {Function} [mSettings.itemFactory] A factory function that will be called whenever the user selects a new entry from the <code>ComboBox</code>. * The factory must return a single control instance of an input based control to provide custom filter capabilities. * This control is then going to be added in the layout provided by the <code>FilterPanel</code>. * <b>Note:</b>: The Panel will not handle the lifecylce of the provided factory control instance, in case the row is going to be * removed, the according consumer needs to decide about destroying or keeping the control instance. In addition, the <code>getIdForLabel</code> * method can be used to return a focusable children control to provide the <code>labelFor</code> reference. * * @class * The <code>FilterController</code> entity serves as a base class to create personalization implementations that are specific to filtering. * * @extends sap.m.p13n.SelectionController * * @author SAP SE * @version 1.146.0 * @since 1.121 * @public * @alias sap.m.p13n.FilterController */ const FilterController = BaseController.extend("sap.m.p13n.FilterController", { constructor: function (mSettings) { BaseController.apply(this, arguments); this._itemFactory = mSettings?.itemFactory; this._bResetEnabled = true; } }); FilterController.prototype.getCurrentState = function () { const oXConfig = xConfigAPI.readConfig(this.getAdaptationControl()) || {}; const aConditions = oXConfig.hasOwnProperty("properties") ? oXConfig.properties.filterConditions : []; return aConditions?.reduce((mConditions, oState) => { const sKey = oState.key; mConditions[sKey] = mConditions[sKey] || []; mConditions[sKey].push(oState.condition); return mConditions; }, {}) || {}; }; FilterController.prototype.getChangeOperations = () => { return { add: "addCondition", remove: "removeCondition" }; }; FilterController.prototype._getPresenceAttribute = (bexternalAppliance) => { return "active"; }; FilterController.prototype.initAdaptationUI = function (oPropertyHelper, oWrapper) { return new Promise((resolve, reject) => { sap.ui.require(["sap/m/p13n/FilterPanel", "sap/m/Input"], (FilterPanel, Input) => { const oAdaptationData = this.mixInfoAndState(oPropertyHelper); const oFilterPanel = new FilterPanel({ enableReorder: false, itemFactory: (oItem) => { return this._itemFactory instanceof Function ? this._itemFactory(oItem, oFilterPanel) : new Input({ value: "{$p13n>conditions/0/values/0}" }); } }); oFilterPanel.setP13nData(oAdaptationData.items); this._oPanel = oFilterPanel; resolve(oFilterPanel); }); }); }; const _hasProperty = (aPropertyInfo, sName) => { return aPropertyInfo.some((oProperty) => { //First check unique name let bValid = oProperty.key === sName || oProperty.name === sName || sName == "$search"; //Use path as Fallback bValid = bValid ? bValid : oProperty.path === sName; return bValid; }); }; /** * Searches for `oCondition` in `aConditions` and returns its index (or -1 if not found). * @param {sap.m.p13n.FilterStateItem} oCondition * @param {sap.m.p13n.FilterStateItem[]} aConditions * @returns {int} Index of `oCondition` in `aConditions` */ FilterController.prototype._indexOfCondition = function (oCondition, aConditions) { const oExistingCondition = aConditions.find((oExistingCondition) => oExistingCondition.operator == oCondition.operator && oExistingCondition.values[0] == oCondition.values[0]); return aConditions.indexOf(oExistingCondition); }; FilterController.prototype._createConditionChange = function (sChangeType, oControl, sFieldPath, oCondition) { delete oCondition.filtered; //Consider moving this to the delta calculation instead const oConditionChange = { selectorElement: oControl, changeSpecificData: { changeType: sChangeType, content: this._createConditionChangeContent(sFieldPath, oCondition) } }; return oConditionChange; }; FilterController.prototype._createConditionChangeContent = (sFieldPath, oCondition) => { return { key: sFieldPath, condition: oCondition }; }; /** * Generates a set of changes based on the given conditions * * @param {string} sFieldPath The relevant fieldPath * @param {sap.m.p13n.FilterStateItem[]} aConditions The conditions after they have been changed * @param {sap.m.p13n.FilterStateItem[]} aOrigShadowConditions The conditions before they have been changed * @param {sap.ui.core.Control} oControl Control instance which is being used to generate the changes * @param {boolean} [bAbsoluteAppliance] Indicates whether the appliance should also implicitly remove entries in case they are not provided in the new state * * @returns {array} Array containing the delta based created changes */ FilterController.prototype._diffConditionPath = function (sFieldPath, aConditions, aOrigShadowConditions, oControl, bAbsoluteAppliance) { let oChange; const aChanges = []; const aOrigConditions = merge([], aConditions); const aShadowConditions = aOrigShadowConditions ? merge([], aOrigShadowConditions) : []; if (deepEqual(aConditions, aShadowConditions)) { return aChanges; } const fnRemoveSameConditions = (aConditions, aShadowConditions) => { let bRunAgain; do { bRunAgain = false; for (let i = 0; i < aConditions.length; i++) { const oNewCondition = aConditions[i]; const nConditionIdx = this._indexOfCondition(oNewCondition, aShadowConditions); if (nConditionIdx > -1) { aConditions.splice(i, 1); if (bAbsoluteAppliance) { aShadowConditions.splice(nConditionIdx, 1); } bRunAgain = true; break; } } } while (bRunAgain); }; fnRemoveSameConditions(aConditions, aShadowConditions); if ((aConditions.length > 0) || (aShadowConditions.length > 0)) { aShadowConditions.forEach((oCondition) => { //In case of absolute appliance always remove, in case of explicit appliance only remove if explicitly given in the new state via filtered=false const iNewCondition = this._indexOfCondition(oCondition, aOrigConditions); const bNewConditionExplicitlyRemoved = iNewCondition > -1 && aOrigConditions[iNewCondition].filtered === false; if (bAbsoluteAppliance || bNewConditionExplicitlyRemoved) { oChange = this._createConditionChange("removeCondition", oControl, sFieldPath, oCondition); aChanges.push(oChange); } }); aConditions.forEach((oCondition) => { if (bAbsoluteAppliance || (!oCondition.hasOwnProperty("filtered") || oCondition.filtered !== false)) { oChange = this._createConditionChange("addCondition", oControl, sFieldPath, oCondition); aChanges.push(oChange); } }); } return aChanges; }; /** * Generates a set of changes based on the given arrays for a specified control * * @param {object} mDeltaInfo Map containing the necessary information to calculate the diff as change objects * @param {sap.m.p13n.FilterState} mDeltaInfo.existingState An array describing the control state before a adaptation * @param {sap.m.p13n.FilterState} mDeltaInfo.changedState An array describing the control state after a certain adaptation * @param {sap.ui.core.Control} mDeltaInfo.control Control instance which is being used to generate the changes * @param {boolean} [mDeltaInfo.applyAbsolute=true] Indicates whether the appliance should also implicitly remove entries in case they are not provided in the new state * @param {object} mDeltaInfo.changeOperations Map containing the changeOperations for the given Control instance * @param {string} mDeltaInfo.changeOperations.add Name of the control specific 'add' changehandler * @param {string} mDeltaInfo.changeOperations.remove Name of the control specific 'remove' changehandler * @param {string} [mDeltaInfo.changeOperations.move] Name of the control specific 'move' changehandler * @param {string} [mDeltaInfo.generator] Name of the change generator (E.g. the namespace of the UI creating the change object) * * @returns {array} Array containing the delta based created changes */ FilterController.prototype.getConditionDeltaChanges = function (mDeltaInfo) { let aConditionChanges = []; const mNewConditionState = mDeltaInfo.changedState; const mPreviousConditionState = mDeltaInfo.existingState; const oAdaptationControl = mDeltaInfo.control; const bAbsoluteAppliance = mDeltaInfo.hasOwnProperty("applyAbsolute") ? mDeltaInfo.applyAbsolute : true; const aPropertyInfo = mDeltaInfo.propertyInfo; for (const sFieldPath in mNewConditionState) { const bValidProperty = _hasProperty(aPropertyInfo, sFieldPath); if (!bValidProperty && oAdaptationControl.isA("sap.ui.mdc.Control") && oAdaptationControl.isPropertyHelperFinal()) { Log.warning("property '" + sFieldPath + "' not supported"); continue; } const aFilterConditionChanges = this._diffConditionPath(sFieldPath, mNewConditionState[sFieldPath], mPreviousConditionState[sFieldPath], oAdaptationControl, bAbsoluteAppliance); aConditionChanges = aConditionChanges.concat(aFilterConditionChanges); } return aConditionChanges; }; FilterController.prototype.getDelta = function (mPropertyBag) { const { existingState } = mPropertyBag; let { changedState } = mPropertyBag; if (deepEqual(existingState, changedState)) { return []; } if (changedState instanceof Array) { changedState = changedState.reduce((mConditions, oState) => { const sKey = oState.key; mConditions[sKey] = mConditions[sKey] || []; oState.conditions.forEach((oConditionForKey) => { if (oConditionForKey && oConditionForKey.values && oConditionForKey.values[0] !== undefined) { mConditions[sKey].push(oConditionForKey); } }); return mConditions; }, {}); } return this.getConditionDeltaChanges({ ...mPropertyBag, changedState }); }; FilterController.prototype._getChangeContent = (oProperty, aDeltaAttributes) => { const oChangeContent = {}; aDeltaAttributes.forEach((sAttribute) => { if (oProperty.hasOwnProperty(sAttribute)) { oChangeContent[sAttribute] = oProperty[sAttribute]; } }); return oChangeContent; }; FilterController.prototype.mixInfoAndState = function (oPropertyHelper) { const mExistingFilters = this.getCurrentState() || {}; const oP13nData = this.prepareAdaptationData(oPropertyHelper, (mItem, oProperty) => { const aExistingFilters = mExistingFilters[mItem.name]; mItem.conditions = aExistingFilters || (this._itemFactory ? [] : [{ operator: "Contains", values: [] }]); mItem.active = aExistingFilters && aExistingFilters.length > 0; return !(oProperty.filterable === false); }); this.sortP13nData({ visible: "active", position: undefined }, oP13nData.items); return oP13nData; }; FilterController.prototype.changesToState = function (aChanges) { const mStateDiff = {}; aChanges.forEach((oChange) => { const oStateDiffContent = merge({}, oChange.changeSpecificData.content); const sKey = oStateDiffContent.key; if (!mStateDiff[sKey]) { mStateDiff[sKey] = []; } //set the presence attribute to false in case of an explicit remove if (oChange.changeSpecificData.changeType === this.getChangeOperations()["remove"]) { oStateDiffContent.condition.filtered = false; } mStateDiff[sKey].push(oStateDiffContent.condition); }); return mStateDiff; }; return FilterController; });