@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,394 lines (1,211 loc) • 54 kB
JavaScript
/*!
* 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([
"sap/m/p13n/modules/AdaptationProvider",
"sap/base/util/merge",
"sap/base/Log",
"sap/m/p13n/modification/FlexModificationHandler",
"sap/m/p13n/MessageStrip",
"sap/ui/core/Element",
"sap/ui/core/ElementRegistry",
"sap/ui/core/message/MessageType",
"sap/m/p13n/modules/DefaultProviderRegistry",
"sap/m/p13n/modules/UIManager",
"sap/m/p13n/modules/StateHandlerRegistry",
"sap/m/p13n/modules/xConfigAPI",
"sap/m/p13n/enums/ProcessingStrategy",
"sap/m/p13n/enums/PersistenceMode"
], (
AdaptationProvider,
merge,
Log,
FlexModificationHandler,
MessageStrip,
Element,
ElementRegistry,
MessageType,
DefaultProviderRegistry,
UIManager,
StateHandlerRegistry,
xConfigAPI,
ProcessingStrategy,
PersistenceMode
) => {
"use strict";
const ERROR_INSTANCING = "Engine: This class is a singleton. Please use the getInstance() method instead.";
/*global WeakMap */
const _mRegistry = new WeakMap();
//Singleton storage
let 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.146.0
* @public
* @since 1.104
*/
const 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
*/
/**
*
* 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.MetadataHelper MetadataHelper}</code> to provide metadata-specific information. It may be used to define more granular information for the selection of items.
* @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.");
}
let oRegistryEntry = this._getRegistryEntry(oControl);
if (oRegistryEntry) {
this.deregister(oControl);
}
oRegistryEntry = this._createRegistryEntry(oControl, oConfig);
const aControllerKeys = Object.keys(oConfig.controller);
aControllerKeys.forEach((sKey) => {
const 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);
}
});
//In case the control is marked as modified, the state change event is triggered once initially to apply the default state
const oXConfig = oControl.getCustomData().find((oCustomData) => oCustomData.getKey() == "xConfig");
if (oXConfig && JSON.parse((oXConfig.getValue()).replace(/\\/g, ''))?.modified) {
this.fireStateChange(oControl);
}
};
/**
* 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) {
const oRegistryEntry = this._getRegistryEntry(oControl);
//destroy subcontroller
Object.keys(oRegistryEntry.controller).forEach((sKey) => {
const 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
const 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 = async function(oControl, vPanelKeys, mSettings) {
const enableReset = await this.hasChanges(oControl, vPanelKeys)
.catch((oError) => {
return false;
});
const oDialog = await this.uimanager.show(oControl, vPanelKeys, {
...mSettings,
enableReset
});
const oPopup = oDialog.getParent();
if (!(vPanelKeys instanceof Array)) {
const oController = this.getController(oControl, vPanelKeys);
oController?.enhancePopup?.(oPopup);
}
return oPopup;
};
/**
* The state for changes of the <code>SelectionController</code>.
*
* @typedef {object} module:sap/m/p13n/Engine$StateChangeEventSelectionState
* @property {string} key The key of the item affected
* @public
* @since 1.140.0
*/
/**
* The state for changes of the <code>SortController</code>.
*
* @typedef {object} module:sap/m/p13n/Engine$StateChangeEventSortState
* @property {string} key The key of the affected sort order
* @property {number} index The position of the sort order
* @property {boolean} descending Indicates whether the sort order is descending
* @public
* @since 1.140.0
*/
/**
* The state for changes of the <code>GroupController</code>.
*
* @typedef {object} module:sap/m/p13n/Engine$StateChangeEventGroupState
* @property {string} key The key of the affected group order
* @property {number} index The position of the group order
* @public
* @since 1.140.0
*/
/**
* The state for changes of the <code>FilterController</code>.
* The keys of the object are the filter keys used in the <code>Engine</code> registration. The values are arrays of {@link sap.ui.mdc.condition.ConditionObject ConditionObject}.
*
* @typedef {Object<string, sap.m.p13n.FilterStateItem[]>} module:sap/m/p13n/Engine$StateChangeEventFilterState
* @public
* @since 1.140.0
*/
/**
* The personalization state change event.
*
* @typedef {object} module:sap/m/p13n/Engine$StateChangeEvent
* @property {sap.ui.core.Control} [control] Control for which the state change event was fired.
* @property {Object<string, Array<module:sap/m/p13n/Engine$StateChangeEventSelectionState> | Array<module:sap/m/p13n/Engine$StateChangeEventSortState> | Array<module:sap/m/p13n/Engine$StateChangeEventGroupState> | Array<module:sap/m/p13n/Engine$StateChangeEventFilterState> | Array<any> >} [state] Changed (delta) state of the control. The keys of the object refer to the controller keys used in the <code>Engine</code> registration. The values can be an array of any of the following types:
* <ul>
* <li>{@link module:sap/m/p13n/Engine$StateChangeEventSelectionState StateChangeEventSelectionState}</li>
* <li>{@link module:sap/m/p13n/Engine$StateChangeEventSortState StateChangeEventSortState}</li>
* <li>{@link module:sap/m/p13n/Engine$StateChangeEventGroupState StateChangeEventGroupState}</li>
* <li>{@link module:sap/m/p13n/Engine$StateChangeEventFilterState StateChangeEventFilterState}</li>
* <li>Custom controller state definitions</li>
* </ul>
* @public
* @since 1.140.0
*/
/**
* 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(module:sap/m/p13n/Engine$StateChangeEvent):void} fnStateEventHandler The handler function to call when the event occurs
* @param {object} [oListener] The context object to call the event handler with (value of <code>this</code> in the event handler function).
* @returns {this} Returns <code>this</code> to allow method chaining
*/
Engine.prototype.attachStateChange = function(fnStateEventHandler, oListener) {
return this.stateHandlerRegistry.attachChange(fnStateEventHandler, oListener);
};
/**
* 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
* @param {object} [oListener] The context object to call the event handler with (value of <code>this</code> in the event handler function).
* @returns {this} Returns <code>this</code> to allow method chaining
*/
Engine.prototype.detachStateChange = function(fnStateEventHandler, oListener) {
return this.stateHandlerRegistry.detachChange(fnStateEventHandler, oListener);
};
/**
* Check if there are changes for a given control instance
*
* @private
* @ui5-restricted sap.m, sap.ui.mdc
*
* @param {sap.ui.core.Control} control The control instance
* @param {string} key The affected controller key
* @returns {Promise<boolean>} A Promise that resolves if the given control instance has applied changes
*/
Engine.prototype.hasChanges = function(control, key) {
const oChangeOperations = this.getController(control, key)?.getChangeOperations();
let changeTypes;
if (oChangeOperations) {
changeTypes = [];
Object.values(oChangeOperations).forEach((vChangeOperation) => {
if (Array.isArray(vChangeOperation)) {
changeTypes = changeTypes.concat(vChangeOperation);
} else {
changeTypes.push(vChangeOperation);
}
});
}
let selectors = [];
if (this.getController(control, key)?.getSelectorsForHasChanges) {
selectors = selectors.concat(this.getController(control, key).getSelectorsForHasChanges());
} else {
selectors.push(control);
}
const oModificationSetting = this._determineModification(control);
return this.getModificationHandler(control).hasChanges({
selector: control,
selectors,
changeTypes
}, oModificationSetting?.payload).then((enableReset) => {
return enableReset;
});
};
/**
* 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 = async function(oControl, aKeys) {
if (aKeys === undefined) {
aKeys = this.getRegisteredControllers(oControl);
}
aKeys = aKeys instanceof Array ? aKeys : [aKeys];
let aSelectors = [];
aKeys.forEach((sKey) => {
aSelectors = aSelectors.concat(this.getController(oControl, sKey).getSelectorForReset());
});
const oResetConfig = {
selectors: aSelectors,
selector: oControl
};
if (aKeys) {
let aChangeTypes = [];
aKeys.forEach((sKey) => {
aChangeTypes = aChangeTypes.concat(Object.values(this.getController(oControl, sKey).getChangeOperations()));
});
oResetConfig.changeTypes = [].concat(...aChangeTypes);
}
const oModificationSetting = this._determineModification(oControl);
await oModificationSetting.handler.reset(oResetConfig, oModificationSetting.payload);
// re-init housekeeping after update
const oPropertyHelper = await this.initAdaptation(oControl, aKeys);
const aUpdatePromises = aKeys.map((sKey) => {
const oController = this.getController(oControl, sKey);
return oController.update(oPropertyHelper);
});
const oPromise = new Promise((resolve) => {
Promise.all(aUpdatePromises).then(() => {
resolve(null);
});
});
return oPromise;
};
/**
*
* @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
*
* @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 grouping 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((oCurrentState) => {
const aStatePromise = [];
let aChanges = [];
let mInfoState = {};
if (oControl.validateState instanceof Function) {
mInfoState = oControl.validateState(this.externalizeKeys(oControl, oState));
}
if (mInfoState.validation === MessageType.Error) {
Log.error(mInfoState.message);
}
const aKeys = Object.keys(oState);
aKeys.forEach((sControllerKey) => {
const 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;
}
const oStatePromise = this.createChanges({
control: oControl,
key: sControllerKey,
state: oController.sanityCheck(oState[sControllerKey]),
suppressAppliance: true,
applyAbsolute: false
});
aStatePromise.push(oStatePromise);
});
return Promise.all(aStatePromise).then((aRawChanges) => {
const mChangeMap = {};
aRawChanges.forEach((aSpecificChanges, iIndex) => {
if (aSpecificChanges && aSpecificChanges.length > 0) {
aChanges = aChanges.concat(aSpecificChanges);
const sKey = aKeys[iIndex];
mChangeMap[sKey] = aSpecificChanges;
}
});
return this._processChanges(oControl, mChangeMap);
});
});
};
/**
*
* 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
* @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(() => {
//ensure that all changes have been applied
return Engine.getInstance().waitForChanges(oControl).then(() => {
const oRetrievedState = {};
Engine.getInstance().getRegisteredControllers(oControl).forEach((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");
}
const oModificationSetting = this._determineModification(vControl); //check and calculate modification basics
oModificationSetting.handler = oModificationHandler;
this._getRegistryEntry(vControl).modification = oModificationSetting;
};
Engine.prototype._addToQueue = function(oControl, fTask) {
const oRegistryEntry = this._getRegistryEntry(oControl);
const fCleanupPromiseQueue = (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.enums.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) {
const oControl = Engine.getControlInstance(mDiffParameters.control);
const sKey = mDiffParameters.key;
const vNewState = mDiffParameters.state;
const bSuppressCallback = !!mDiffParameters.suppressAppliance;
if (!sKey || !mDiffParameters.control || !vNewState) {
return Promise.resolve([]);
}
const fDeltaHandling = () => {
return this.initAdaptation(oControl, sKey).then(() => {
return vNewState;
})
.then((aNewState) => {
const oController = this.getController(oControl, sKey);
const mChangeOperations = oController.getChangeOperations();
const oRegistryEntry = this._getRegistryEntry(oControl);
const oCurrentState = oController.getCurrentState();
const oPriorState = merge(oCurrentState instanceof Array ? [] : {}, oCurrentState);
const oControllerHelper = oController.getMetadataHelper();
const oHelper = oControllerHelper ? oControllerHelper : oRegistryEntry.helper;
const oPropertyInfo = oHelper.getProperties().map((a) => {
return {
key: a.key,
name: a.name
};
});
const mDeltaConfig = {
existingState: mDiffParameters.stateBefore || oPriorState,
applyAbsolute: mDiffParameters.applyAbsolute,
changedState: aNewState,
control: oController.getAdaptationControl(),
changeOperations: mChangeOperations,
deltaAttributes: ["key"],
propertyInfo: oPropertyInfo
};
//Only execute change calculation in case there is a difference (--> example: press 'Ok' without a difference)
const aChanges = oController.getDelta(mDeltaConfig);
if (!bSuppressCallback) {
const mChangeMap = {};
mChangeMap[sKey] = aChanges;
return this._processChanges(oControl, mChangeMap)
.then(() => {
return aChanges;
});
}
return aChanges || [];
});
};
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) {
const oModificationSetting = this._determineModification(oControl);
const oRegistryEntry = this._getRegistryEntry(oControl);
return oRegistryEntry && oRegistryEntry.pendingModification ? oRegistryEntry.pendingModification : Promise.resolve()
.then(() => {
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) {
const oModificationSetting = this._determineModification(oControl);
return oModificationSetting.handler.isModificationSupported({
element: oControl
}, oModificationSetting.payload);
};
Engine.prototype.fireStateChange = function(oControl) {
return this.retrieveState(oControl).then((oState) => {
this.stateHandlerRegistry.fireChange(oControl, oState);
});
};
/**
* 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) {
let aChanges = [];
const aKeys = Object.keys(mChanges);
const oDiff = {};
aKeys.forEach((sKey) => {
oDiff[sKey] = this.getController(vControl, sKey).changesToState(mChanges[sKey]);
aChanges = aChanges.concat(mChanges[sKey]);
});
if (aChanges instanceof Array && aChanges.length > 0) {
const oModificationSetting = this._determineModification(vControl);
return oModificationSetting.handler.processChanges(aChanges, oModificationSetting.payload);
} 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) {
let fResolveRTA;
const oOriginalModifHandler = this.getModificationHandler(oControl);
const oTemporaryRTAHandler = new FlexModificationHandler();
const oRTAPromise = new Promise((resolve, reject) => {
fResolveRTA = resolve;
});
oTemporaryRTAHandler.processChanges = (aChanges) => {
fResolveRTA(aChanges);
return Promise.resolve(aChanges);
};
this._setModificationHandler(oControl, oTemporaryRTAHandler);
this.uimanager.show(oControl, aKeys, {
showReset: false,
refreshPropertyHelper: true
}).then((oContainer) => {
const 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(() => {
this._setModificationHandler(oControl, oOriginalModifHandler);
oTemporaryRTAHandler.destroy();
});
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) {
const oControl = Engine.getControlInstance(vControl);
const oRegistryEntry = this._getRegistryEntry(vControl);
const sPersistenceIdentifier = mEnhanceConfig?.value?.controllerKey ?? mEnhanceConfig?.value?.persistenceIdentifier;
mEnhanceConfig.currentState = Engine.getInstance().getController(oControl, mEnhanceConfig.changeType, sPersistenceIdentifier)?.getCurrentState();
return xConfigAPI.enhanceConfig(oControl, mEnhanceConfig)
.then((oConfig) => {
if (oRegistryEntry) {
//to simplify debugging
oRegistryEntry.xConfig = oConfig;
}
return 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 = (vControl, mEnhanceConfig) => {
const 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) {
const oExternalState = {};
Object.keys(oInternalState).forEach((sInternalKey) => {
const oController = this.getController(Engine.getControlInstance(vControl), sInternalKey);
if (oController) {
const oStateKey = oController.getStateKey();
if (oExternalState.hasOwnProperty(oStateKey)) {
merge(oExternalState[oStateKey], oInternalState[sInternalKey]);
} else {
oExternalState[oController.getStateKey()] = oInternalState[sInternalKey];
}
}
});
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) {
const aControllerKeys = this.getRegisteredControllers(vControl),
oInternalState = {};
aControllerKeys.forEach((sInternalRegistryKey) => {
const oController = this.getController(vControl, sInternalRegistryKey);
const sExternalStateKey = oController.getStateKey();
if (oExternalState.hasOwnProperty(sExternalStateKey)) {
const oState = oController.formatToInternalState(oExternalState[sExternalStateKey]);
if (oState) {
oInternalState[sInternalRegistryKey] = oState;
}
}
});
return oInternalState;
};
Engine.prototype.diffState = function(oControl, oOld, oNew) {
const aDiffCreation = [],
oDiffState = {};
oOld = merge({}, oOld);
oNew = merge({}, oNew);
Object.keys(oNew).forEach((sKey) => {
aDiffCreation.push(this.createChanges({
control: oControl,
stateBefore: oOld[sKey],
state: this.getController(oControl, sKey).sanityCheck(oNew[sKey]),
applyAbsolute: ProcessingStrategy.FullReplace,
key: sKey,
suppressAppliance: true
}));
});
return Promise.all(aDiffCreation)
.then((aChanges) => {
Object.keys(oNew).forEach((sKey, i) => {
if (oNew[sKey]) {
const aState = this.getController(oControl, sKey).changesToState(aChanges[i], oOld[sKey], oNew[sKey]);
oDiffState[sKey] = aState;
}
});
return oDiffState;
});
};
Engine.prototype.checkControlInitialized = (vControl) => {
const oControl = Engine.getControlInstance(vControl);
const pInitialize = oControl.initialized instanceof Function ? oControl.initialized() : Promise.resolve();
return pInitialize || Promise.resolve();
};
Engine.prototype.checkPropertyHelperInitialized = (vControl) => {
const 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 {boolean | undefined} bRefreshPropertyHelper 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, bRefreshPropertyHelper) {
this.verifyController(vControl, aKeys);
//1) Cache property helper
const oRegistryEntry = this._getRegistryEntry(vControl);
const oControl = Engine.getControlInstance(vControl);
if (!bRefreshPropertyHelper && oRegistryEntry.helper) {
return Promise.resolve(oRegistryEntry.helper);
}
return this.checkPropertyHelperInitialized(oControl).then((oPropertyHelper) => {
oRegistryEntry.helper = oPropertyHelper;
return oPropertyHelper;
}, (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) {
const 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.
* @param {string} [sPersistenceIdentifier] The key defined for the controller. Should be used if <code>sKey</code> represents the changeType.
*
* @returns {sap.m.p13n.SelectionController} The controller instance
*/
Engine.prototype.getController = function(vControl, sKey, sPersistenceIdentifier) {
const oRegistryEntry = this._getRegistryEntry(vControl);
let oController;
if (oRegistryEntry && oRegistryEntry.controller.hasOwnProperty(sKey)) {
oController = oRegistryEntry.controller[sKey];
}
if (oController) {
return oController;
}
this.getRegisteredControllers(vControl).forEach((sController) => {
const oRegisteredController = this.getController(vControl, sController);
if (oRegisteredController) {
Object.keys(oRegisteredController.getChangeOperations()).forEach((sOperationType) => {
if (oRegisteredController.getChangeOperations()[sOperationType] === sKey) {
if (!sPersistenceIdentifier || sPersistenceIdentifier === oRegisteredController.getPersistenceIdentifier()) {
oController = oRegisteredController;
}
}
});
}
});
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) {
const aKeys = vKey instanceof Array ? vKey : [vKey];
aKeys.forEach((sKey) => {
if (!this.getController(vControl, sKey)) {
const oControl = Engine.getControlInstance(vControl);
throw new Error("No controller registered yet for " + oControl.getId() + " and key: " + sKey);
}
});
};
/**
* 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) {
const aKeys = Array.isArray(vKeys) ? vKeys : [vKeys];
this.verifyController(vControl, aKeys);
const oPropertyHelper = this._getRegistryEntry(vControl).helper;
const mUiSettings = {},
aPanelCreation = [];
aKeys.forEach((sKey) => {
const oController = this.getController(vControl, sKey);
const pAdaptationUI = oController.initAdaptationUI(oPropertyHelper);
//Check faceless controller implementations and skip them
if (pAdaptationUI instanceof Promise) {
aPanelCreation.push(pAdaptationUI);
}
});
return Promise.all(aPanelCreation)
.then((aPanels) => {
aPanels.forEach((oPanel, iIndex) => {
const 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) {
const 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) {
const 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) {
const 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 = (vControl) => {
const 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) {
const 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 = (vControl, oPreConfig) => {
const 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) {
const oRegistryEntry = this._getRegistryEntry(vControl);
this.getRegisteredControllers(vControl).forEach((sKey) => {
const oController = this.getController(vControl, sKey);
const mChangeOperations = oController.getChangeOperations();
Object.keys(mChangeOperations).forEach((sType) => {
if (mChangeOperations[sType] === oChange.changeSpecificData.changeType) {
oRegistryEntry.pendingAppliance[sKey] = [].concat(oRegistryEntry.pendingAppliance[sKey] || []).concat(oChange);
}
});
});
};
Engine.prototype.getTrace = function(vControl, oChange) {
const oRegistryEntry = this._getRegistryEntry(vControl);
let oTrace;
if (oRegistryEntry) {
oTrace = Object.keys(oRegistryEntry.pendingAppliance);
}
return oTrace;
};
Engine.prototype.clearTrace = function(vControl, oChange) {
const oRegistryEntry = this._getRegistryEntry(vControl);
if (oRegistryEntry) {
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) {
const oRegistryEntry = this._getRegistryEntry(vControl);
//Modification setting is only calculated once per control instance
if (oRegistryEntry && oRegistryEntry.modification) {
return oRegistryEntry.modification;
}
const aPPResults = this.hasForReference(vControl, "sap.m.p13n.PersistenceProvider").concat(this.hasForReference(vControl, "sap.ui.mdc.p13n.PersistenceProvider"));
const aVMResults = this.hasForReference(vControl, "sap.ui.fl.variants.VariantManagement");
let oRelevantPersistenceProvider;
if (aPPResults?.length > 1) {
oRelevantPersistenceProvider = aPPResults.find((oProvider) => {
const aDefaultProviders = Object.values(this.defaultProviderRegistry._mDefaultProviders);
return aDefaultProviders?.find((oDefaultProvider) => oDefaultProvider.getId() == oProvider.getId());
});
}
oRelevantPersistenceProvider = oRelevantPersistenceProvider || aPPResults?.[0];
const sHandlerMode = oRelevantPersistenceProvider ? oRelevantPersistenceProvider.getMode() : "Standard";
const 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;
};
/**
* Checks if the control's configuration allows for change persistence
*
* @private
* @param {string|sap.ui.core.Control} vControl The control id or instance
* @returns {boolean} Returns whether global persistence is enabled
*/
Engine.prototype._getKeyUserPersistence = function(vControl) {
const {mode, hasVM} = oEngine._determineModification(vControl);
let bGlobalPersistence = mode === PersistenceMode.Global;
if (mode === PersistenceMode.Auto || mode === PersistenceMode.Transient) {
bGlobalPersistence = hasVM ? false : true;
}
return bGlobalPersistence;
};
Engine.prototype.hasForReference = (vControl, sControlType) => {
const sControlId = vControl && vControl.getId ? vControl.getId() : vControl;
const aResults = ElementRegistry.filter((oElement) => {
if (!oElement.isA(sControlType)) {
return false;
}
const aFor = oElement.getFor instanceof Function ? oElement.getFor() : [];
for (let 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 = (sControlId, sAncestorControlId) => {
let oControl;
if (sControlId === sAncestorControlId) {
return true;
}
oControl = Element.getElementById(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 = (vControl) => {
return typeof vControl == "string" ? Element.getElementById(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) {
const oController = this.g