UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,376 lines (1,177 loc) 48.1 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ "sap/m/p13n/modules/AdaptationProvider", "sap/base/util/merge", "sap/base/Log", "sap/m/p13n/modification/FlexModificationHandler", "sap/m/MessageStrip", "sap/ui/core/library", "sap/ui/core/Element", "sap/m/p13n/modules/DefaultProviderRegistry", "sap/m/p13n/modules/UIManager", "sap/m/p13n/modules/StateHandlerRegistry", "sap/m/p13n/modules/xConfigAPI", "sap/m/p13n/enum/ProcessingStrategy" ], function (AdaptationProvider, merge, Log, FlexModificationHandler, MessageStrip, coreLibrary, Element, DefaultProviderRegistry, UIManager, StateHandlerRegistry, xConfigAPI, ProcessingStrategy) { "use strict"; var ERROR_INSTANCING = "Engine: This class is a singleton. Please use the getInstance() method instead."; //Shortcut to 'MessageType' var MessageType = coreLibrary.MessageType; /*global WeakMap */ var _mRegistry = new WeakMap(); //Singleton storage var oEngine; /** * * @class * The <code>Engine</code> entity offers personalization capabilities by registering a control instance for modification, such as: * * <ul> * <li><code>sap.m.p13n.Popup</code> initialization</li> * <li>Storing personalization states by choosing the desired persistence layer</li> * <li>State appliance considering the persistence layer</li> * </ul> * * The Engine must be used whenever personalization should be enabled by taking a certain persistence layer into account. * Available controller implementations for the registration process are: * * <ul> * <li>{@link sap.m.p13n.SelectionController SelectionController}: Used to define a list of selectable entries</li> * <li>{@link sap.m.p13n.SortController SortController}: Used to define a list of sortable properties</li> * <li>{@link sap.m.p13n.GroupController GroupController}: Used to define a list of groupable properties</li> * </ul> * * Can be used in combination with <code>sap.ui.fl.variants.VariantManagement</code> to persist a state in variants using <code>sap.ui.fl</code> capabilities.</li> * * @see {@link topic:75c08fdebf784575947927e052712bab Personalization} * @alias sap.m.p13n.Engine * @extends sap.m.p13n.modules.AdaptationProvider * @author SAP SE * @version 1.117.4 * @public * @since 1.104 */ var Engine = AdaptationProvider.extend("sap.m.p13n.Engine", { constructor: function () { AdaptationProvider.call(this); if (oEngine) { throw Error(ERROR_INSTANCING); } this._aRegistry = []; this._aStateHandlers = []; //Default Provider Registry to be used for internal PersistenceProvider functionality access this.defaultProviderRegistry = DefaultProviderRegistry.getInstance(this); //UIManager to be used for p13n UI creation this.uimanager = UIManager.getInstance(this); //Default state Handler Registry to be used for state event handling this.stateHandlerRegistry = StateHandlerRegistry.getInstance(); } }); /** * The <code>sap.m.p13n</code> namespace offers generic personalization capabilites. * Personalization currently supports, for example, defining the order of columns in a table and their visibility, sorting, and grouping. To enable this, the personalization engine can be used. * @namespace * @name sap.m.p13n * @public */ /** * @namespace * @name sap.m.p13n.MetadataHelper * @public */ /** * * The central registration for personalization functionality. * The registration is a precondition for using <code>Engine</code> functionality for a control instance. * Once the control instance has been registered, it can be passed to the related <code>Engine</code> * methods that always expect a control instance as parameter. Only registered control instances can be used for personalization through the <code>Engine</code>. * @public * @typedef {object} sap.m.p13n.EngineRegistrationConfig * @property {sap.m.p13n.MetadataHelper} helper The <code>{@link sap.m.p13n.modification.MetadataHelper MetadataHelper}</code> to provide metadata-specific information. * @property {Object<string,sap.m.p13n.SelectionController>} controller A map of arbitrary keys that contain a controller instance as value. The key must be unique and needs to be provided for later access when using <code>Engine</code> functionality specific for one controller type. */ /** * @public * * @param {sap.ui.core.Control} oControl The control instance to be registered for adaptation * @param {sap.m.p13n.EngineRegistrationConfig} oConfig The Engine registration configuration * * @example * { * helper: new Helper([ * {key: "idA", label: "Field A", path: "path/propA"}, * {key: "idB", label: "Field B", path: "path/propB"} * ]), * controller: { * Item: new SelectionController({ * control: oMyControl, * targetAggregation: "items" * }), * Sort: new SortController({ * control: oMyControl * }), * Filter: new GroupController({ * control: oMyControl * }) * } * } */ Engine.prototype.register = function (oControl, oConfig) { if (!oConfig.hasOwnProperty("controller")/* || Object.keys(oConfig.controller).length < 1*/) { throw new Error("Please provide at least a configuration 'controller' containing a map of key-value pairs (key + Controller class) in order to register adaptation."); } var oRegistryEntry = this._getRegistryEntry(oControl); if (oRegistryEntry) { this.deregister(oControl); } oRegistryEntry = this._createRegistryEntry(oControl, oConfig); var aControllerKeys = Object.keys(oConfig.controller); aControllerKeys.forEach(function (sKey) { var oSubController = oConfig.controller[sKey]; if (!this.getController(oControl, sKey)) { if (this._aRegistry.indexOf(oControl.getId()) < 0) { this._aRegistry.push(oControl.getId()); } this.addController(oSubController, sKey); } }.bind(this)); }; /** * Unregisters a registered control. By unregistering a control the control is * removed from the <code>Engine</code> registry, and all instance-specific submodules, * such as the registered controllers, are destroyed. * * @public * * @param {sap.ui.core.Control} oControl The registered control instance */ Engine.prototype.deregister = function (oControl) { var oRegistryEntry = this._getRegistryEntry(oControl); //destroy subcontroller Object.keys(oRegistryEntry.controller).forEach(function (sKey) { var oController = oRegistryEntry.controller[sKey]; oController.destroy(); delete oRegistryEntry.controller[sKey]; }); //Remove the control from the weakmap housekeeping _mRegistry.delete(oControl); //Remove the control from the array to maintain debugging var iControlIndex = this._aRegistry.indexOf(oControl.getId()); this._aRegistry.splice(iControlIndex, 1); }; /** * Opens the personalization dialog. * * @public * * * @param {sap.ui.core.Control} oControl The control instance that is personalized * @param {string|string[]} vPanelKeys The affected panels that are added to the <code>sap.m.p13n.Popup</code> * @param {object} mSettings The settings object for the personalization * @param {string} [mSettings.title] The title for the <code>sap.m.p13n.Popup</code> control * @param {sap.ui.core.Control} [mSettings.source] The source control to be used by the <code>sap.m.p13n.Popup</code> control (only necessary if the mode is set to <code>ResponsivePopover</code>) * @param {object} [mSettings.mode] The mode is used by the <code>sap.m.p13n.Popup</code> control * @param {object} [mSettings.contentHeight] Height configuration for the related popup container * @param {object} [mSettings.contentWidth] Width configuration for the related popup container * * @returns {Promise<sap.m.p13n.Popup>} Promise resolving in the <code>sap.m.p13n.Popup</code> instance */ Engine.prototype.show = function (oControl, vPanelKeys, mSettings) { return this.getModificationHandler(oControl).hasChanges({selector: oControl}).then((enableReset) => { return enableReset; }) .catch((oError) => { return false; }) .then(function(enableReset){ return this.uimanager.show(oControl, vPanelKeys, {...mSettings, enableReset}); }.bind(this)); }; /** * Attaches an event handler to the <code>StateHandlerRegistry</code> class. * The event handler is fired every time a user triggers a personalization change for a control instance during runtime. * * @public * * @param {function(sap.ui.base.Event):void} fnStateEventHandler The handler function to call when the event occurs * @returns {this} Returns <code>this</code> to allow method chaining */ Engine.prototype.attachStateChange = function (fnStateEventHandler) { return this.stateHandlerRegistry.attachChange(fnStateEventHandler); }; /** * Removes a previously attached state change event handler from the <code>StateHandlerRegistry</code> class. * The passed parameters must match those used for registration with {@link sap.m.p13n.Engine#attachStateChange} beforehand. * * @public * * @param {function(sap.ui.base.Event):void} fnStateEventHandler The handler function to detach from the event * @returns {this} Returns <code>this</code> to allow method chaining */ Engine.prototype.detachStateChange = function (fnStateEventHandler) { return this.stateHandlerRegistry.detachChange(fnStateEventHandler); }; /** * This method can be used to trigger a reset to the provided control instance. * * @public * * @param {sap.ui.core.Control} oControl The related control instance * @param {string} aKeys The key for the affected configuration * * @returns {Promise<null>} A Promise resolving once the reset is completed */ Engine.prototype.reset = function (oControl, aKeys) { if (aKeys === undefined) { aKeys = this.getRegisteredControllers(oControl); } aKeys = aKeys instanceof Array ? aKeys : [aKeys]; var aSelectors = []; aKeys.forEach(function (sKey) { aSelectors = aSelectors.concat(this.getController(oControl, sKey).getSelectorForReset()); }.bind(this)); var oResetConfig = { selectors: aSelectors, selector: oControl }; if (aKeys) { var aChangeTypes = []; aKeys.forEach(function (sKey) { aChangeTypes = aChangeTypes.concat(Object.values(this.getController(oControl, sKey).getChangeOperations())); }.bind(this)); oResetConfig.changeTypes = [].concat.apply([], aChangeTypes); } var oModificationSetting = this._determineModification(oControl); return oModificationSetting.handler.reset(oResetConfig, oModificationSetting.payload).then(function () { this.stateHandlerRegistry.fireChange(oControl); //Re-Init housekeeping after update return this.initAdaptation(oControl, aKeys).then(function (oPropertyHelper) { aKeys.forEach(function (sKey) { var oController = this.getController(oControl, sKey); oController.update(oPropertyHelper); }.bind(this)); }.bind(this)); }.bind(this)); }; /** * * @public * @typedef {object} sap.m.p13n.State * @property {Object<string,Object[]>} controller A map of arbitrary keys that contain a controller instance as value. The key must be unique and needs to be provided for later access when using <code>Engine</code> functionality specific for one controller type. */ /** * Applies a state to a control by passing an object that contains the * registered controller key and an object matching the inner subcontroller logic. * * @public * @experimental Since 1.104. Please note that the API of this control is not yet finalized! * * @example { * RegisteredSortControllerKey: [{ * key: "key1" //Adds key1 to the existing sorting * }, * { * key: "key2", * sorted: false //Removes sorting for key2 * },{ * key: "key3", * position: 2 //Reorders current sorter position in the array * }], * RegisteredSelectionControllerKey: [{ * key: "key1" //Adds key1 to the existing selection status * }, * { * key: "key2", * visible: false //Removes selection status for key2 * },{ * key: "key3", * position: 2 //Reorders current item position in the array * }], * RegisteredGroupControllerKey: [{ * key: "key1" //Adds key1 to the existing grouping status * }, * { * key: "key2", * grouped: false //Removes selection status for key2 * },{ * key: "key3", * position: 2 //Reorders current grouping position in the array * } * } * * @param {sap.ui.core.Control} oControl The registered control instance * @param {sap.m.p13n.State} oState The state object * * @returns {Promise<sap.m.p13n.State>} A Promise resolving after the state has been applied */ Engine.prototype.applyState = function (oControl, oState) { //Call retrieve only to ensure that the control is initialized and enabled for modification return this.retrieveState(oControl).then(function (oCurrentState) { var aStatePromise = [], aChanges = [], mInfoState = {}; if (oControl.validateState instanceof Function) { mInfoState = oControl.validateState(this.externalizeKeys(oControl, oState)); } if (mInfoState.validation === MessageType.Error) { Log.error(mInfoState.message); } var aKeys = Object.keys(oState); aKeys.forEach(function (sControllerKey) { var oController = this.getController(oControl, sControllerKey); if (!oController) { //In case no controller can be found, skip change creation & appliance //to avoid errors and react gracefully return; } var oStatePromise = this.createChanges({ control: oControl, key: sControllerKey, state: oController.sanityCheck(oState[sControllerKey]), suppressAppliance: true, applyAbsolute: false }); aStatePromise.push(oStatePromise); }.bind(this)); return Promise.all(aStatePromise).then(function (aRawChanges) { var mChangeMap = {}; aRawChanges.forEach(function (aSpecificChanges, iIndex) { if (aSpecificChanges && aSpecificChanges.length > 0) { aChanges = aChanges.concat(aSpecificChanges); var sKey = aKeys[iIndex]; mChangeMap[sKey] = aSpecificChanges; } }); return this._processChanges(oControl, mChangeMap); }.bind(this)); }.bind(this)); }; /** * * Retrieves the state for a given control instance * after all necessary changes have been applied (e.g. modification handler appliance). * After the returned <code>Promise</code> has been resolved, the returned state is in sync with the related * state object of the control. * * @public * @experimental Since 1.104. Please note that the API of this control is not yet finalized! * @param {sap.ui.core.Control} oControl The control instance implementing IxState to retrieve the externalized state * * @returns {Promise<sap.m.p13n.State>} A Promise resolving in the current control state */ Engine.prototype.retrieveState = function (oControl) { //ensure that the control has been initialized return this.checkControlInitialized(oControl).then(function () { //ensure that all changes have been applied return Engine.getInstance().waitForChanges(oControl).then(function () { var oRetrievedState = {}; Engine.getInstance().getRegisteredControllers(oControl).forEach(function (sKey) { oRetrievedState[sKey] = Engine.getInstance().getController(oControl, sKey).getCurrentState(true); }); return merge({}, oRetrievedState); }); }); }; /** * This method can be used to set the modification handling for a control instance. * @private * * @param {sap.ui.core.Control} vControl The registered control instance * @param {sap.m.p13n.modification.ModificationHandler} oModificationHandler The modification handler object */ Engine.prototype._setModificationHandler = function (vControl, oModificationHandler) { if (!oModificationHandler.isA("sap.m.p13n.modification.ModificationHandler")) { throw new Error("Only sap.m.p13n.modification.ModificationHandler derivations are allowed for modification"); } var oModificationSetting = this._determineModification(vControl); //check and calculate modification basics oModificationSetting.handler = oModificationHandler; this._getRegistryEntry(vControl).modification = oModificationSetting; }; Engine.prototype._addToQueue = function (oControl, fTask) { var oRegistryEntry = this._getRegistryEntry(oControl); var fCleanupPromiseQueue = function (pOriginalPromise) { if (oRegistryEntry.pendingModification === pOriginalPromise) { oRegistryEntry.pendingModification = null; } }; oRegistryEntry.pendingModification = oRegistryEntry.pendingModification instanceof Promise ? oRegistryEntry.pendingModification.then(fTask) : fTask(); oRegistryEntry.pendingModification.then(fCleanupPromiseQueue.bind(null, oRegistryEntry.pendingModification)); return oRegistryEntry.pendingModification; }; /** * <code>Engine#createChanges</code> can be used to programmatically trigger the creation * of a set of changes based on the current control state and the provided state. * * @private * @ui5-restricted sap.m, sap.ui.mdc * * @param {object} mDiffParameters A map defining the configuration to create the changes * @param {sap.ui.core.Control} mDiffParameters.control The control instance that should be adapted * @param {string} mDiffParameters.key The key used to retrieve the related controller * @param {object} mDiffParameters.state The state that is applied to the provided control instance * @param {sap.m.p13n.enum.ProcessingStrategy} [mDiffParameters.applyAbsolute] Determines about the comparison algorithm between two states * @param {boolean} [mDiffParameters.stateBefore] If the state should be diffed manually; * for example, if "A" exists in the control state, but is not mentioned in the new state provided in the * mDiffParameters.state then the absolute appliance decides whether to remove "A" or to keep it. * @param {boolean} [mDiffParameters.suppressAppliance] Determines whether the change is applied directly * @param {boolean} [mDiffParameters.applySequentially] Determines whether the appliance is queued or processed in parallel * controller * * @returns {Promise} A Promise resolving in the related delta changes */ Engine.prototype.createChanges = function (mDiffParameters) { var oControl = Engine.getControlInstance(mDiffParameters.control); var sKey = mDiffParameters.key; var vNewState = mDiffParameters.state; var bSuppressCallback = !!mDiffParameters.suppressAppliance; if (!sKey || !mDiffParameters.control || !vNewState) { return Promise.resolve([]); } var fDeltaHandling = function () { return this.initAdaptation(oControl, sKey).then(function () { return vNewState; }) .then(function (aNewState) { var oController = this.getController(oControl, sKey); var mChangeOperations = oController.getChangeOperations(); var oRegistryEntry = this._getRegistryEntry(oControl); var oCurrentState = oController.getCurrentState(); var oPriorState = merge(oCurrentState instanceof Array ? [] : {}, oCurrentState); var mDeltaConfig = { existingState: mDiffParameters.stateBefore || oPriorState, applyAbsolute: mDiffParameters.applyAbsolute, changedState: aNewState, control: oController.getAdaptationControl(), changeOperations: mChangeOperations, deltaAttributes: ["key"], propertyInfo: oRegistryEntry.helper.getProperties().map(function (a) { return { key: a.key, name: a.name }; }) }; //Only execute change calculation in case there is a difference (--> example: press 'Ok' without a difference) var aChanges = oController.getDelta(mDeltaConfig); if (!bSuppressCallback) { var mChangeMap = {}; mChangeMap[sKey] = aChanges; return this._processChanges(oControl, mChangeMap) .then(function () { return aChanges; }); } return aChanges || []; }.bind(this)); }.bind(this); return this._addToQueue(oControl, fDeltaHandling); }; /** * Returns a promise resolving after all currently pending modifications have been applied. * * @private * @ui5-restricted sap.m, sap.ui.mdc * * @param {sap.ui.core.Control} oControl The related control instance * @returns {Promise} A Promise resolving after all pending modifications have been applied */ Engine.prototype.waitForChanges = function (oControl) { var oModificationSetting = this._determineModification(oControl); var oRegistryEntry = this._getRegistryEntry(oControl); return oRegistryEntry && oRegistryEntry.pendingModification ? oRegistryEntry.pendingModification : Promise.resolve() .then(function () { return oModificationSetting.handler.waitForChanges({ element: oControl }, oModificationSetting.payload); }); }; /** * Determines whether the environment is suitable for the desired modification of the provided control instance. * * @private * @ui5-restricted sap.m, sap.ui.mdc * @param {sap.ui.core.Control} oControl The related control instance * * @returns {Promise} A Promise resolving in a Boolean that determines whether the requirements for the persistence layer are met */ Engine.prototype.isModificationSupported = function (oControl) { var oModificationSetting = this._determineModification(oControl); return oModificationSetting.handler.isModificationSupported({ element: oControl }, oModificationSetting.payload); }; Engine.prototype.fireStateChange = function (oControl) { return this.retrieveState(oControl).then(function (oState) { this.stateHandlerRegistry.fireChange(oControl, oState); }.bind(this)); }; /** * This method can be used to process an array of changes. * @private * * @param {sap.ui.core.Control} vControl The registered control instance * @param {object} mChanges A map of keys and arrays, every controller will provide an array of changes * @returns {Promise} The change appliance <code>Promise</code> */ Engine.prototype._processChanges = function (vControl, mChanges) { var aChanges = []; var aKeys = Object.keys(mChanges); var oDiff = {}; aKeys.forEach(function (sKey) { oDiff[sKey] = this.getController(vControl, sKey).changesToState(mChanges[sKey]); aChanges = aChanges.concat(mChanges[sKey]); }.bind(this)); if (aChanges instanceof Array && aChanges.length > 0) { var oModificationSetting = this._determineModification(vControl); return oModificationSetting.handler.processChanges(aChanges, oModificationSetting.payload) .then(function (aChanges) { var oControl = Engine.getControlInstance(vControl); this.fireStateChange(oControl); return aChanges; }.bind(this)); } else { return Promise.resolve([]); } }; /** * This method can be used in the designtime metadata of the control * for key user personalization. * * @private * @ui5-restricted sap.m, sap.ui.mdc * * @param {sap.ui.core.Control} oControl The registered control instance * @param {object} mPropertyBag The property bag provided in the settings action * @param {string} aKeys The keys to be used to display in the corresponding controller * * @returns {Promise} A Promise resolving in the set of changes to be created during UI adaptation at runtime */ Engine.prototype.getRTASettingsActionHandler = function (oControl, mPropertyBag, aKeys) { var fResolveRTA; //var aVMs = this.hasForReference(oControl, "sap.ui.fl.variants.VariantManagement"); // TODO: clarify if we need this error handling / what to do with the Link if we want to keep it var aPVs = this.hasForReference(oControl, "sap.m.p13n.PersistenceProvider"); if (aPVs.length > 0 && !oControl.isA("sap.ui.mdc.link.Panel")) { return Promise.reject("Please do not use a PeristenceProvider in RTA."); } var oOriginalModifHandler = this.getModificationHandler(oControl); var oTemporaryRTAHandler = new FlexModificationHandler(); var oRTAPromise = new Promise(function (resolve, reject) { fResolveRTA = resolve; }); oTemporaryRTAHandler.processChanges = function (aChanges) { fResolveRTA(aChanges); return Promise.resolve(aChanges); }; this._setModificationHandler(oControl, oTemporaryRTAHandler); this.uimanager.show(oControl, aKeys, { showReset: false }).then(function (oContainer) { var oCustomHeader = oContainer.getCustomHeader(); if (oCustomHeader) { oCustomHeader.getContentRight()[0].setVisible(false); } oContainer.addStyleClass(mPropertyBag.styleClass); if (mPropertyBag.fnAfterClose instanceof Function) { oContainer.attachAfterClose(mPropertyBag.fnAfterClose); } }); oRTAPromise.then(function () { this._setModificationHandler(oControl, oOriginalModifHandler); oTemporaryRTAHandler.destroy(); }.bind(this)); return oRTAPromise; }; /** * Enhances the <code>xConfig</code> object by using the <code>ModificationHandler</code>. * * @private * @ui5-restricted sap.m, sap.ui.mdc * * @param {sap.ui.core.Control} vControl The registered control instance * @param {object} mEnhanceConfig An object providing the information about the xConfig enhancement * @param {object} mEnhanceConfig.key The affected property name * @param {object} mEnhanceConfig.controlMeta Object describing which config is affected * @param {object} mEnhanceConfig.controlMeta.aggregation The affected aggregation name (such as <code>columns</code> or <code>filterItems</code>) * @param {object} mEnhanceConfig.controlMeta.property The affected property name (such as <code>width</code> or <code>label</code>) * @param {object} mEnhanceConfig.value The value that should be written in the xConfig * @param {object} [mEnhanceConfig.propertyBag] Optional property bag for the <code>ModificationHandler</code> * @returns {Promise} Promise resolving when the xConfig is successfully enhanced */ Engine.prototype.enhanceXConfig = function (vControl, mEnhanceConfig) { var oControl = Engine.getControlInstance(vControl); var oRegistryEntry = this._getRegistryEntry(vControl); mEnhanceConfig.currentState = Engine.getInstance().getController(oControl, mEnhanceConfig.changeType)?.getCurrentState(); return xConfigAPI.enhanceConfig(oControl, mEnhanceConfig) .then(function (oConfig) { if (oRegistryEntry) { //to simplify debugging oRegistryEntry.xConfig = oConfig; } }); }; /** * Returns a copy of the <code>xConfig</code> object. * * @private * @ui5-restricted sap.m, sap.ui.mdc * @param {sap.ui.core.Element} vControl The related element that should be checked * @param {object} [mEnhanceConfig] An object providing a modification-handler-specific payload * @param {object} [mEnhanceConfig.propertyBag] Optional property bag for different modification handler derivations * * @returns {Promise<object>|object} * A <code>Promise</code> that resolves with the configuration. The xConfig itself if it is already available, or <code>null</code> if there is no xConfig */ Engine.prototype.readXConfig = function (vControl, mEnhanceConfig) { var oControl = Engine.getControlInstance(vControl); return xConfigAPI.readConfig(oControl, mEnhanceConfig) || {}; }; /** * The Engine is processing state via the internal key registry. * The external state representation might differ from the internal registration. * <b>Note:</b> This will only replace the keys to the external StateUtil representation, but not transform the state content itself. * * @private * @param {string|sap.ui.core.Control} vControl The registered control instance * @param {object} oInternalState The internal state * @returns {object} The externalized state */ Engine.prototype.externalizeKeys = function (vControl, oInternalState) { var oExternalState = {}; Object.keys(oInternalState).forEach(function (sInternalKey) { var oController = this.getController(Engine.getControlInstance(vControl), sInternalKey); if (oController) { oExternalState[oController.getStateKey()] = oInternalState[sInternalKey]; } }.bind(this)); return oExternalState; }; /** * The Engine is processing state via the internal key registry. * The external state representation might differ from the internal registration. * <b>Note:</b> This will only replace the keys to the internal Engine registry, but not transform the state content itself. * * @private * @param {string|sap.ui.core.Control} vControl The registered control instance * @param {object} oExternalState The external state * @returns {object} The internalized state */ Engine.prototype.internalizeKeys = function (vControl, oExternalState) { var aControllerKeys = this.getRegisteredControllers(vControl), oInternalState = {}; aControllerKeys.forEach(function (sInternalRegistryKey) { var sExternalStateKey = this.getController(vControl, sInternalRegistryKey).getStateKey(); if (oExternalState.hasOwnProperty(sExternalStateKey)) { oInternalState[sInternalRegistryKey] = oExternalState[sExternalStateKey]; } }.bind(this)); return oInternalState; }; Engine.prototype.diffState = function (oControl, oOld, oNew) { var aDiffCreation = [], oDiffState = {}; oOld = merge({}, oOld); oNew = merge({}, oNew); Object.keys(oNew).forEach(function (sKey) { aDiffCreation.push(this.createChanges({ control: oControl, stateBefore: oOld[sKey], state: oNew[sKey], applyAbsolute: ProcessingStrategy.FullReplace, key: sKey, suppressAppliance: true })); }.bind(this)); return Promise.all(aDiffCreation) .then(function (aChanges) { Object.keys(oNew).forEach(function (sKey, i) { if (oNew[sKey]) { var aState = this.getController(oControl, sKey).changesToState(aChanges[i], oOld[sKey], oNew[sKey]); oDiffState[sKey] = aState; } }.bind(this)); return oDiffState; }.bind(this)); }; Engine.prototype.checkControlInitialized = function (vControl) { var oControl = Engine.getControlInstance(vControl); return oControl.initialized instanceof Function ? oControl.initialized() : Promise.resolve(); }; Engine.prototype.checkPropertyHelperInitialized = function (vControl) { var oControl = Engine.getControlInstance(vControl); return oControl.initPropertyHelper instanceof Function ? oControl.initPropertyHelper() : Promise.resolve(); }; /** * This method can be used to initialize the controller housekeeping. * * @private * * @param {sap.ui.core.Control} vControl The registered control instance * @param {string|string[]} aKeys The key for the related controller * @param {Object[]} aCustomInfo A custom set of propertyinfos as base to create the UI * * @returns {Promise} A Promise resolving after the adaptation housekeeping has been initialized */ Engine.prototype.initAdaptation = function (vControl, aKeys) { this.verifyController(vControl, aKeys); //1) Cache property helper var oRegistryEntry = this._getRegistryEntry(vControl); var oControl = Engine.getControlInstance(vControl); if (oRegistryEntry.helper) { return Promise.resolve(oRegistryEntry.helper); } return this.checkPropertyHelperInitialized(oControl).then(function (oPropertyHelper) { oRegistryEntry.helper = oPropertyHelper; return oPropertyHelper; }, function (sHelperError) { throw new Error(sHelperError); }); }; /** * This method should only be used to register a new controller. * * @private * * @param {sap.m.p13n.subcontroller.Controller} oController The controller instance. * @param {string} sKey The key that defines the later access to the controller instance. * @param {object} oPreConfig A predefined configuration */ Engine.prototype.addController = function (oController, sKey, oPreConfig) { var oRegistryEntry = this._getRegistryEntry(oController.getAdaptationControl(), oPreConfig); oRegistryEntry.controller[sKey] = oController; }; /** * This method can be used to get a controller instance. * @private * * @param {sap.ui.core.Control} vControl The registered Control instance. * @param {string} sKey The key/changeType for which the controller has been registered. * * @returns {sap.m.p13n.SelectionController} The controller instance */ Engine.prototype.getController = function (vControl, sKey) { var oRegistryEntry = this._getRegistryEntry(vControl), oController; if (oRegistryEntry && oRegistryEntry.controller.hasOwnProperty(sKey)) { oController = oRegistryEntry.controller[sKey]; } if (!oController) { this.getRegisteredControllers(vControl).forEach(function (sController) { var oRegisteredController = this.getController(vControl, sController); if (oRegisteredController) { Object.keys(oRegisteredController.getChangeOperations()).forEach(function (sOperationType) { if (oRegisteredController.getChangeOperations()[sOperationType] === sKey) { oController = oRegisteredController; } }); } }.bind(this)); } return oController; }; /** * Verifies the existence of a set of subcontrollers registered for a provided control instance. * * @param {sap.ui.core.Control} vControl The registered Control instance. * @param {string|array} vKey A key as string or an array of keys */ Engine.prototype.verifyController = function (vControl, vKey) { var aKeys = vKey instanceof Array ? vKey : [vKey]; aKeys.forEach(function (sKey) { if (!this.getController(vControl, sKey)) { var oControl = Engine.getControlInstance(vControl); throw new Error("No controller registered yet for " + oControl.getId() + " and key: " + sKey); } }.bind(this)); }; /** * Retrieves the subcontroller UI settings for a provided control instance * and the set of provided registered keys. * * @param {sap.ui.core.Control} vControl The registered Control instance. * @param {string|string[]} vKeys A key as string or an array of keys * * @returns {object} The requested UI settings of the control instance and provided keys */ Engine.prototype.getUISettings = function (vControl, vKeys) { var aKeys = Array.isArray(vKeys) ? vKeys : [vKeys]; this.verifyController(vControl, aKeys); var oPropertyHelper = this._getRegistryEntry(vControl).helper; var mUiSettings = {}, aPanelCreation = []; aKeys.forEach(function (sKey) { var oController = this.getController(vControl, sKey); var pAdaptationUI = oController.initAdaptationUI(oPropertyHelper); //Check faceless controller implementations and skip them if (pAdaptationUI instanceof Promise) { aPanelCreation.push(pAdaptationUI); } }.bind(this)); return Promise.all(aPanelCreation) .then(function (aPanels) { aPanels.forEach(function (oPanel, iIndex) { var sKey = aKeys[iIndex]; mUiSettings[sKey] = { panel: oPanel }; }); return mUiSettings; }); }; /** * This method can be used to determine if a control is registered in the Engine. * * @private * @ui5-restricted sap.m * * @param {string|sap.ui.core.Control} vControl The control ID or instance * @returns {boolean} true if modification settings were already determined */ Engine.prototype.isRegistered = function (vControl) { var oRegistryEntry = this._getRegistryEntry(vControl); return !!oRegistryEntry; }; /** * This method can be used to determine if modification settings for a control have already been created. * * @private * * @param {sap.ui.core.Control} vControl The registered Control instance * @returns {boolean} true if modification settings were already determined */ Engine.prototype.isRegisteredForModification = function (vControl) { var oRegistryEntry = this._getRegistryEntry(vControl); return oRegistryEntry && !!oRegistryEntry.modification; }; /** * Returns an array of all registered controllers * * @param {string|sap.ui.core.Control} vControl The control ID or instance * @returns {array} An array of all registered controller instances */ Engine.prototype.getRegisteredControllers = function (vControl) { var oRegistryEntry = this._getRegistryEntry(vControl); return oRegistryEntry ? Object.keys(oRegistryEntry.controller) : []; }; /** * This method can be used to get the registry entry for a control instance * * @private * @param {string|sap.ui.core.Control} vControl The control id or instance * * @returns {object} The related registry entry */ Engine.prototype._getRegistryEntry = function (vControl) { var oControl = Engine.getControlInstance(vControl); return _mRegistry.get(oControl); }; /** * This method can be used to get the modification handling for a control instance * * @private * @ui5-restricted sap.m, sap.ui.mdc * * @param {string|sap.ui.core.Control} vControl The control id or instance * @returns {object} The related ModificationHandler. */ Engine.prototype.getModificationHandler = function (vControl) { var oModificationSetting = this._determineModification(vControl); //This method might also be retrieved by non-registered Controls (such as FilterBarBase) - the default should always be Flex. return oModificationSetting.handler; }; /** * This method can be used to create the registry entry for a control instance * * @private * @param {string|sap.ui.core.Control} vControl The control id or instance * @param {object} oPreConfig A predefined configuration * @returns {object} The related registry entry */ Engine.prototype._createRegistryEntry = function (vControl, oPreConfig) { var oControl = Engine.getControlInstance(vControl); if (!_mRegistry.has(oControl)) { _mRegistry.set(oControl, { modification: oPreConfig && oPreConfig.modification ? { handler: oPreConfig.modification, payload: { mode: "Auto", hasVM: true, hasPP: false } } : null, controller: {}, activeP13n: null, helper: oPreConfig && oPreConfig.helper ? oPreConfig.helper : null, xConfig: null, pendingAppliance: {} }); } return _mRegistry.get(oControl); }; Engine.prototype.trace = function (vControl, oChange) { var oRegistryEntry = this._getRegistryEntry(vControl); this.getRegisteredControllers(vControl).forEach(function (sKey) { var oController = this.getController(vControl, sKey); var mChangeOperations = oController.getChangeOperations(); Object.keys(mChangeOperations).forEach(function (sType) { if (mChangeOperations[sType] === oChange.changeSpecificData.changeType) { oRegistryEntry.pendingAppliance[sKey] = [].concat(oRegistryEntry.pendingAppliance[sKey] || []).concat(oChange); } }); }.bind(this)); }; Engine.prototype.getTrace = function (vControl, oChange) { var oRegistryEntry = this._getRegistryEntry(vControl); return Object.keys(oRegistryEntry.pendingAppliance); }; Engine.prototype.clearTrace = function (vControl, oChange) { var oRegistryEntry = this._getRegistryEntry(vControl); oRegistryEntry.pendingAppliance = {}; }; /** * Determines and registers the ModificationHandler per control instance * * @private * @param {string|sap.ui.core.Control} vControl The control id or instance * @returns {object} The related modification registry entry */ Engine.prototype._determineModification = function (vControl) { var oRegistryEntry = this._getRegistryEntry(vControl); //Modification setting is only calculated once per control instance if (oRegistryEntry && oRegistryEntry.modification) { return oRegistryEntry.modification; } var aPPResults = this.hasForReference(vControl, "sap.m.p13n.PersistenceProvider").concat(this.hasForReference(vControl, "sap.ui.mdc.p13n.PersistenceProvider")); var aVMResults = this.hasForReference(vControl, "sap.ui.fl.variants.VariantManagement"); var aPersistenceProvider = aPPResults.length ? aPPResults : undefined; var sHandlerMode = aPersistenceProvider ? aPersistenceProvider[0].getMode() : "Standard"; var oModificationSetting = { handler: FlexModificationHandler.getInstance(), payload: { hasVM: aVMResults && aVMResults.length > 0, hasPP: aPPResults && aPPResults.length > 0, mode: sHandlerMode } }; if (oRegistryEntry && !oRegistryEntry.modification) { oRegistryEntry.modification = oModificationSetting; } return oModificationSetting; }; Engine.prototype.hasForReference = function (vControl, sControlType) { var sControlId = vControl && vControl.getId ? vControl.getId() : vControl; var aResults = Element.registry.filter(function (oElement) { if (!oElement.isA(sControlType)) { return false; } var aFor = oElement.getFor instanceof Function ? oElement.getFor() : []; for (var n = 0; n < aFor.length; n++) { if (aFor[n] === sControlId || oEngine.hasControlAncestorWithId(sControlId, aFor[n])) { return true; } } return false; }); return aResults; }; /** * Determines and registeres the ModificationHandler per control instance * * @private * @param {string} sControlId The control id * @param {string} sAncestorControlId The control ancestor id * * @returns {boolean} Returns whether an related ancestor could be found. */ Engine.prototype.hasControlAncestorWithId = function (sControlId, sAncestorControlId) { var oControl; if (sControlId === sAncestorControlId) { return true; } oControl = sap.ui.getCore().byId(sControlId); while (oControl) { if (oControl.getId() === sAncestorControlId) { return true; } if (typeof oControl.getParent === "function") { oControl = oControl.getParent(); } else { return false; } } return false; }; /** * This method can be used to get a control instance by passing either the control * or the Control's ID. * * @private * * @param {string|sap.ui.core.Control} vControl The control ID or instance * @returns {sap.ui.core.Control} The control instance */ Engine.getControlInstance = function (vControl) { return typeof vControl == "string" ? sap.ui.getCore().byId(vControl) : vControl; }; /** * This method can be used to get the active p13n state of a registered Control. * E.g. the method will return the key of the controller that is currently being * used to display a p13n UI. * * @private * @param {string|sap.ui.core.Control} vControl The control ID or instance * * @returns {boolean} The related flag is the Control has an open P13n container */ Engine.prototype.hasActiveP13n = function (vControl) { return !!this._getRegistryEntry(vControl).activeP13n; }; /** * This method can be used to set the active p13n state of a registered Control. * E.g. the method will return the key of the controller that is currently being * used to display a p13n UI. * * @private * * @param {sap.ui.core.Control} vControl The registered control instance. * @param {string} sKey The registered key to get the corresponding controller. * @param {boolean} bModified Determines whether changes have been triggered while the dialog has been opened */ Engine.prototype.setActiveP13n = function (vControl, sKey, bModified) { this._getRegistryEntry(vControl).activeP13n = sKey ? { usedControllers: sKey, modified: bModified } : null; }; /** * Triggers a validation for a certain controller - The method will create a * MessageStrip and place it on the related oP13nUI. The BaseController needs * to implement <code>BaseController#validateP13n</code>. * * @private * * @param {sap.ui.core.Control} vControl The registered control instance. * @param {string} sKey The registered key to get the corresponding controller. * @param {sap.ui.core.Control} oP13nUI The adaptation UI displayed in the container (e.g. BasePanel derivation). */ Engine.prototype.validateP13n = function (vControl, sKey, oP13nUI) { var oController = this.getController(vControl, sKey); var oControl = Engine.getControlInstance(vControl); var mControllers = this._getRegistryEntry(vControl).controller; var oTheoreticalState = {}; Object.keys(mControllers).forEach(function (sControllerKey) { oTheoreticalState[sControllerKey] = mControllers[sControllerKey].getCurrentState(); }); //Only execute validation for controllers that support 'model2State' if (oController && oController.model2State instanceof Function) { oTheoreticalState[sKey] = oController.model2State(); var mInfoState = { validation: MessageType.None }; if (oControl.validateState instanceof Function) { mInfoState = oControl.validateState(this.externalizeKeys(oControl, oTheoreticalState), sKey); } var oMessageStrip; if (mInfoState.validation !== MessageType.None) { oMessageStrip = new MessageStrip({ type: mInfoState.validation, text: mInfoState.message }); } if (oP13nUI.setMessageStrip instanceof Function) { oP13nUI.setMessageStrip(oMessageStrip); } else { Log.warning("message strip could not be provided - the adaptation UI needs to implement 'setMessageStrip'"); } } }; /** * Reads the current state of the subcontrollers and triggers a state appliance * * @param {sap.ui.core.Control} oControl The registered Control instance. * @param {string[]} aKeys An array of keys * @returns {Promise} A Promise resolving after all p13n changes have been calculated and processed */ Engine.prototype.handleP13n = function (oControl, aKeys) { var pChanges = []; aKeys.forEach(function (sControllerKey) { var oController = this.getController(oControl, sControllerKey); var vP13nData = oController.getP13nData(); if (vP13nData) { var p = this.createChanges({ control: oControl, key: sControllerKey, state: vP13nData, suppressAppliance: true, applyAbsolute: true }) .then(function (aItemChanges) { return oController.getBeforeApply().then(function (aChanges) { var aComulatedChanges = aChanges ? aChanges.concat(aItemChanges) : aItemChanges; return aComulatedChanges; }); }); pChanges.push(p); } }.bind(this)); return Promise.all(pChanges).then(function (aChangeMatrix) { var aApplyChanges = []; var mChangeMap = {}; aChangeMatrix.forEach(function (aTypeChanges, iIndex) { aApplyChanges = aApplyChanges.concat(aTypeChanges); var sKey = aKeys[iIndex]; mChangeMap[sKey] = aTypeChanges; }); if (aApplyChanges.length > 0) { Engine.getInstance()._processChanges(oControl, mChangeMap); } }); }; /** * This method is the central point of access to the Engine Singleton. * * @private * @ui5-restricted sap.m, sap.ui.mdc * * @returns {sap.m.p13n.Engine} The Engine instance */ Engine.getInstance = function () { if (!oEngine) { oEngine = new Engine(); } return oEngine; }; /** * This method can be used for debugging to retrieve the complete registry. * * @private * @returns {object} The Engine registry object */ Engine.prototype._getRegistry = function () { var oRegistry = { stateHandlerRegistry: this.stateHandlerRegistry, defaultProviderRegistry: this.defaultProviderRegistry, controlRegistry: {} }; this._aRegistry.forEach(function (sKey) { var oControl = sap.ui.getCore().byId(sKey); oRegistry.controlRegistry[sKey] = _mRegistry.get(oControl); }); return oRegistry; }; /** * @override * @inheritDoc */ Engine.prototype.destroy = function () { AdaptationProvider.prototype.destroy.apply(this, arguments); oEngine = null; this._aRegistry = null; _mRegistry.delete(this); this.defaultProviderRegistry.destroy(); this.defaultProviderRegistry = null; this.stateHandlerRegistry.destroy(); this.stateHandlerRegistry = null; this.uimanager.destroy(); this.uimanager = null; }; return Engine; });