UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

481 lines (424 loc) 19.5 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([ "sap/base/util/merge", "sap/base/util/deepEqual", "sap/ui/core/util/reflection/JsControlTreeModifier" ], (merge, deepEqual, JsControlTreeModifier) => { "use strict"; /** * @namespace * @private * @alias sap.m.p13n.modules.xConfigAPI */ const xConfigAPI = {}; /** * Enhances the xConfig object for a given mdc control instance. * * @param {sap.ui.core.Element} oControl The according element which should be checked * @param {object} oModificationPayload An object providing a modification handler specific payload * @param {object} oModificationPayload.key The affected metadata property key * @param {object} oModificationPayload.controlMeta Object describing which config is affected * @param {object} oModificationPayload.controlMeta.aggregation The affected aggregation name (such as <code>columns</code> or <code>filterItems</code>) * @param {object} oModificationPayload.property The affected property name (such as <code>width</code> or <code>lable</code>) * @param {object} oModificationPayload.value The value that should be written in nthe xConfig * @param {object} [oModificationPayload.propertyBag] Optional propertybag for different modification handler derivations * @param {boolean} [oModificationPayload.markAsModified] Optional flag that triggers a state change event for the engine registration process * * @returns {Promise<object>} Promise resolving to the adapted xConfig object */ xConfigAPI.enhanceConfig = (oControl, oModificationPayload) => { const mPropertyBag = oModificationPayload.propertyBag; const oModifier = mPropertyBag ? mPropertyBag.modifier : JsControlTreeModifier; let oControlMetadata; let oXConfig; return oModifier.getControlMetadata(oControl) .then((oRetrievedControlMetadata) => { oControlMetadata = oRetrievedControlMetadata; oModificationPayload.controlMetadata = oControlMetadata; return oModifier.getAggregation(oControl, "customData"); }) .then((aCustomData) => { return Promise.all(aCustomData.map((oCustomData) => { return oModifier.getProperty(oCustomData, "key"); })).then((aCustomDataKeys) => { return aCustomData.reduce((oResult, mCustomData, iIndex) => { return aCustomDataKeys[iIndex] === "xConfig" ? mCustomData : oResult; }, undefined); }); }) .then((oRetrievedXConfig) => { oXConfig = oRetrievedXConfig; if (oXConfig) { return oModifier.getProperty(oXConfig, "value") .then((sConfig) => { return merge({}, JSON.parse(sConfig.replace(/\\/g, ''))); }); } return {}; }) .then(async (oExistingConfig) => { let oConfig; if (oModificationPayload.controlMeta && oModificationPayload.controlMeta.aggregation) { await xConfigAPI.prepareAggregationConfig(oControl, oModificationPayload, oExistingConfig); oConfig = xConfigAPI.createAggregationConfig(oControl, oModificationPayload, oExistingConfig); } else { oConfig = xConfigAPI.createPropertyConfig(oControl, oModificationPayload, oExistingConfig); } if (oModificationPayload.markAsModified) { oConfig.modified = true; } const oAppComponent = mPropertyBag ? mPropertyBag.appComponent : undefined; let pDelete = Promise.resolve(); if (oXConfig) { pDelete = oModifier.removeAggregation(oControl, "customData", oXConfig) .then(() => { if (oControl.isA) { return oModifier.destroy(oXConfig); } return Promise.resolve(); }); } return pDelete.then(() => { return oModifier.createAndAddCustomData(oControl, "xConfig", JSON.stringify(oConfig), oAppComponent) .then(() => { return merge({}, oConfig); }); }); }); }; async function findAsync(arr, asyncCallback) { const promises = arr.map(asyncCallback); const results = await Promise.all(promises); const index = results.findIndex((result) => result); return arr[index]; } xConfigAPI.getCurrentItemState = async function (oControl, oModificationPayload, oConfig, sAggregationName) { const changeType = oModificationPayload?.changeType; if (!oModificationPayload.propertyBag || !changeType || changeType.indexOf("Item") === -1) { return; } const { modifier, appComponent } = oModificationPayload.propertyBag; const aTargetAggregationItems = await modifier.getAggregation(oControl, sAggregationName); const aAggregationItems = aTargetAggregationItems || []; const aCurrentState = []; if (oConfig?.aggregations?.[sAggregationName] !== undefined && Object.keys(oConfig.aggregations[sAggregationName]).length > 0) { Object.entries(oConfig.aggregations[sAggregationName]).forEach(([sKey, oItem]) => { if (oItem.visible !== false) { aCurrentState.push({ key: sKey, position: oItem.position }); } }); aCurrentState.sort((a, b) => a.position - b.position); aCurrentState.map((o) => delete o.position); } else { await aAggregationItems.reduce(async (pAccum, oItem, iIndex) => { const pCurrentAccum = await pAccum; //synchronize async loop const aCustomData = await modifier.getAggregation(oItem, "customData"); const oAffectedItem = await findAsync(aCustomData, async (oItemCustomData) => { return await modifier.getProperty(oItemCustomData, "key") === "p13nKey"; }); if (oAffectedItem) { const sKey = await modifier.getProperty(oAffectedItem, "value"); const vRelevant = await modifier.getProperty(oItem, "visible"); if (vRelevant && sKey) { aCurrentState.push({ key: sKey }); } } else { const sId = appComponent ? appComponent.getRootControl()?.getLocalId(modifier.getId(oItem)) : modifier.getId(oItem); const vRelevant = await modifier.getProperty(oItem, "visible"); if (vRelevant && sId) { aCurrentState.push({ key: sId }); } } return pCurrentAccum; }, Promise.resolve()); } return aCurrentState; }; xConfigAPI.getCurrentSortState = async function (oControl, oModificationPayload, oConfig, sPropertyName) { const changeType = oModificationPayload?.changeType; if (!oModificationPayload.propertyBag || !changeType || changeType.indexOf("Sort") === -1) { return; } const aCurrentState = []; if (oConfig?.properties?.[sPropertyName] !== undefined && Object.keys(oConfig.properties[sPropertyName]).length > 0) { oConfig.properties[sPropertyName].forEach((oItem, iIndex) => { aCurrentState.push({ key: oItem.key, position: iIndex }); }); aCurrentState .sort((a, b) => a.position - b.position) .map((o) => delete o.position); } return await Promise.resolve(aCurrentState); }; /** * Returns a copy of the xConfig object * * @param {sap.ui.core.Element} oControl The according element which should be checked * @param {object} [oModificationPayload] An object providing a modification handler specific payload * @param {object} [oModificationPayload.propertyBag] Optional propertybag for different modification handler derivations * * @returns {Promise<object>|object} A promise resolving to the adapted xConfig object or the object directly */ xConfigAPI.readConfig = (oControl, oModificationPayload) => { if (oModificationPayload) { const oModifier = oModificationPayload.propertyBag ? oModificationPayload.propertyBag.modifier : JsControlTreeModifier; return oModifier.getAggregation(oControl, "customData") .then((aCustomData) => { return Promise.all(aCustomData.map((oCustomData) => { return oModifier.getProperty(oCustomData, "key"); })).then((aCustomDataKeys) => { return aCustomData.reduce((oResult, mCustomData, iIndex) => { return aCustomDataKeys[iIndex] === "xConfig" ? mCustomData : oResult; }, undefined); }); }) .then((oAggregationConfig) => { if (oAggregationConfig) { return oModifier.getProperty(oAggregationConfig, "value") .then((sValue) => { return merge({}, JSON.parse(sValue.replace(/\\/g, ''))); }); } return null; }); } // These functions are used instead of the modifier to avoid that the // entire call stack is changed to async when it's not needed const fnGetAggregationSync = (oParent, sAggregationName) => { const fnFindAggregation = (oControl, sAggregationName) => { if (oControl) { if (oControl.getMetadata) { const oMetadata = oControl.getMetadata(); const oAggregations = oMetadata.getAllAggregations(); if (oAggregations) { return oAggregations[sAggregationName]; } } } return undefined; }; const oAggregation = fnFindAggregation(oParent, sAggregationName); if (oAggregation) { return oParent[oAggregation._sGetter](); } return undefined; }; const fnGetPropertySync = (oControl, sPropertyName) => { const oMetadata = oControl.getMetadata().getPropertyLikeSetting(sPropertyName); if (oMetadata) { const sPropertyGetter = oMetadata._sGetter; return oControl[sPropertyGetter](); } return undefined; }; const oAggregationConfig = fnGetAggregationSync(oControl, "customData").find((oCustomData) => { return fnGetPropertySync(oCustomData, "key") == "xConfig"; }); const oConfig = oAggregationConfig ? merge({}, JSON.parse(fnGetPropertySync(oAggregationConfig, "value").replace(/\\/g, ''))) : null; return oConfig; }; const updateIndex = function (oControl, oConfig, oModificationPayload) { const key = oModificationPayload.key || oModificationPayload.name; const { persistenceIdentifier } = oModificationPayload.value; const mControlMeta = oModificationPayload.controlMeta; const vValue = oModificationPayload.value; const oControlMetadata = oModificationPayload.controlMetadata || oControl.getMetadata(); const sAffectedAggregation = mControlMeta.aggregation; const sAggregationName = sAffectedAggregation ? sAffectedAggregation : oControlMetadata.getDefaultAggregation().name; const { currentState } = oModificationPayload; const newIndex = vValue.index; const { operation } = oModificationPayload; const updatedState = merge([], currentState); const operationActions = { add: (affectedKey, index, affectedPersistenceIdentifier) => { const obj = { key: affectedKey }; if (affectedPersistenceIdentifier) { obj.persistenceIdentifier = affectedPersistenceIdentifier; } updatedState.splice(index, 0, obj); }, remove: (affectedKey, index) => { const currentItemState = updatedState?.find((item) => item.key == affectedKey); const currentItemIndex = updatedState?.indexOf(currentItemState); if (currentItemIndex > -1) { updatedState.splice(currentItemIndex, 1); } }, move: (affectedKey, index) => { const currentItemState = updatedState?.find((item) => item.key == affectedKey); const currentItemIndex = updatedState?.indexOf(currentItemState); if (currentItemIndex > -1) { const [movedItem] = updatedState.splice(currentItemIndex, 1); updatedState.splice(index, 0, movedItem); } } }; if (currentState instanceof Array && operation && operationActions[operation] instanceof Function) { operationActions[operation](key, newIndex, persistenceIdentifier); } updatedState.forEach((item, index) => { //find the xConfig item with the same key as item.key const xConfigItem = oConfig.aggregations[sAggregationName]?.[item.key]; if (xConfigItem) { xConfigItem.position = index; } else { //find the index of the current item key in currentState const currentItemIndex = currentState?.findIndex((currentItem) => currentItem.key === item.key); if (currentItemIndex !== index) { oConfig.aggregations[sAggregationName][item.key] = { position: index }; } } }); }; xConfigAPI.prepareAggregationConfig = async (oControl, oModificationPayload, oExistingConfig) => { const mControlMeta = oModificationPayload.controlMeta; const oControlMetadata = oModificationPayload.controlMetadata || oControl.getMetadata(); const sAffectedAggregation = mControlMeta.aggregation; const sAggregationName = sAffectedAggregation ? sAffectedAggregation : oControlMetadata.getDefaultAggregation().name; const oConfig = oExistingConfig || {}; if (!oConfig.hasOwnProperty("aggregations")) { oConfig.aggregations = {}; } if (!oConfig.aggregations.hasOwnProperty(sAggregationName)) { if (oControlMetadata.hasAggregation(sAggregationName)) { oConfig.aggregations[sAggregationName] = {}; const currentState = await xConfigAPI.getCurrentItemState(oControl, oModificationPayload, oConfig, sAggregationName); currentState?.forEach((oItem) => { oConfig.aggregations[sAggregationName][oItem.key] = { position: oItem.position }; }); } else { throw new Error("The aggregation " + sAggregationName + " does not exist for" + oControl); } } oModificationPayload.currentState = oModificationPayload.currentState || await xConfigAPI.getCurrentItemState(oControl, oModificationPayload, oConfig, sAggregationName); }; /** * Enhances the xConfig object for a given mdc control instance. * * @param {sap.ui.core.Element} oControl The according element which should be checked * @param {object} oModificationPayload An object providing a modification handler specific payload * @param {object} oModificationPayload.key The affected property name * @param {object} oModificationPayload.controlMeta Object describing which config is affected * @param {object} oModificationPayload.controlMeta.aggregation The affected aggregation name (such as <code>columns</code> or <code>filterItems</code>) * @param {object} oModificationPayload.property The affected property name (such as <code>width</code> or <code>lable</code>) * @param {object} oModificationPayload.value The value that should be written in nthe xConfig * @param {object} [oExistingConfig] Already existing config to be enhanced by the payload * * @returns {object} The adapted xConfig object */ xConfigAPI.createAggregationConfig = (oControl, oModificationPayload, oExistingConfig) => { const sPropertyInfoKey = oModificationPayload.key || oModificationPayload.name; const mControlMeta = oModificationPayload.controlMeta; const sAffectedProperty = oModificationPayload.property; const vValue = oModificationPayload.value; const oControlMetadata = oModificationPayload.controlMetadata || oControl.getMetadata(); const sAffectedAggregation = mControlMeta.aggregation; const sAggregationName = sAffectedAggregation ? sAffectedAggregation : oControlMetadata.getDefaultAggregation().name; const oConfig = oExistingConfig || {}; if (!oConfig.hasOwnProperty("aggregations")) { oConfig.aggregations = {}; } if (!oConfig.aggregations.hasOwnProperty(sAggregationName)) { if (oControlMetadata.hasAggregation(sAggregationName)) { oConfig.aggregations[sAggregationName] = {}; } else { throw new Error("The aggregation " + sAggregationName + " does not exist for" + oControl); } } if (!oConfig.aggregations[sAggregationName].hasOwnProperty(sPropertyInfoKey)) { oConfig.aggregations[sAggregationName][sPropertyInfoKey] = {}; } if (vValue !== null || (vValue && vValue.hasOwnProperty("value") && vValue.value !== null)) { switch (oModificationPayload.operation) { case "move": oConfig.aggregations[sAggregationName][sPropertyInfoKey][sAffectedProperty] = vValue.index; if (vValue.persistenceIdentifier) { oConfig.aggregations[sAggregationName][sPropertyInfoKey]["persistenceIdentifier"] = vValue.persistenceIdentifier; } updateIndex(oControl, oConfig, oModificationPayload); break; case "remove": case "add": default: //Note: consider aligning xConfig value handling between sap.m and sap.ui.mdc if (vValue.hasOwnProperty("value")) { oConfig.aggregations[sAggregationName][sPropertyInfoKey][sAffectedProperty] = vValue.value; if (vValue.index !== undefined) { oConfig.aggregations[sAggregationName][sPropertyInfoKey]["position"] = vValue.index; } if (vValue.persistenceIdentifier) { oConfig.aggregations[sAggregationName][sPropertyInfoKey]["persistenceIdentifier"] = vValue.persistenceIdentifier; } } else { oConfig.aggregations[sAggregationName][sPropertyInfoKey][sAffectedProperty] = vValue; } updateIndex(oControl, oConfig, oModificationPayload); break; } } else { delete oConfig.aggregations[sAggregationName][sPropertyInfoKey][sAffectedProperty]; //Delete empty property name object if (Object.keys(oConfig.aggregations[sAggregationName][sPropertyInfoKey]).length === 0) { delete oConfig.aggregations[sAggregationName][sPropertyInfoKey]; //Delete empty aggregation name object if (Object.keys(oConfig.aggregations[sAggregationName]).length === 0) { delete oConfig.aggregations[sAggregationName]; } } } return oConfig; }; /** * Enhances the xConfig object for a given mdc control instance. * * @param {sap.ui.core.Element} oControl The according element which should be checked * @param {object} oModificationPayload An object providing a modification handler specific payload * @param {object} oModificationPayload.key The affected property name * @param {object} oModificationPayload.property Object describing which config is affected * @param {object} oModificationPayload.value The value that should be written in nthe xConfig * @param {object} [oExistingConfig] Already existing config to be enhanced by the payload * * @returns {object} The adapted xConfig object */ xConfigAPI.createPropertyConfig = (oControl, oModificationPayload, oExistingConfig) => { //var sDataKey = oModificationPayload.key; const vValue = oModificationPayload.value; //var oControlMetadata = oModificationPayload.controlMetadata || oControl.getMetadata(); const sAffectedProperty = oModificationPayload.property; const oConfig = oExistingConfig || {}; if (!oConfig.properties) { oConfig.properties = {}; } if (!oConfig.properties.hasOwnProperty(sAffectedProperty)) { oConfig.properties[sAffectedProperty] = []; } const sOperation = oModificationPayload.operation; const oItem = oConfig.properties[sAffectedProperty].find((oEntry) => { // DINC0728376 / DINC0490163: Property-type specific comparison // For filterConditions: multiple conditions can exist for the same key, // so we need to compare the condition content to find the correct one. // For sortConditions/groupConditions: key is unique, so key comparison is sufficient. // We cannot use deepEqual on the entire entry because the index in the payload // (restore position) differs from the stored index (original position). if (sAffectedProperty === "filterConditions") { return oEntry.key === oModificationPayload.key && deepEqual(oEntry.condition, oModificationPayload.value.condition); } return oEntry.key === oModificationPayload.key; }); if (oItem && sOperation !== "add") { oConfig.properties[sAffectedProperty].splice(oConfig.properties[sAffectedProperty].indexOf(oItem), 1); } if (sOperation !== "remove") { oConfig.properties[sAffectedProperty].splice(oModificationPayload.value.index, 0, vValue); } return oConfig; }; return xConfigAPI; });