@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
499 lines (430 loc) • 17.9 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/base/Log",
"./library",
"sap/ui/core/EventBus",
"sap/ui/thirdparty/jquery",
"sap/ui/core/StaticArea",
"sap/ui/core/Configuration",
"sap/ui/core/Theming"
],
function(Log, mLibrary, EventBus, jQuery, StaticArea, Configuration, Theming) {
"use strict";
/**
* STATIC MEMBERS
*/
var SAP_ILLUSTRATION_POOL_ID = 'sap-ui-illustration-pool',
SAP_ILLUSTRATION_PATTERNS_NAME = '-Patterns',
SAP_ILLUSTRATION_SET_NAME = 'sapIllus',
SAP_ILLUSTRATION_SET_PATH = sap.ui.require.toUrl("sap/m/themes/base/illustrations/"),
SAP_ILLUSTRATION_SET_SYMBOLS = Object.keys(mLibrary.IllustratedMessageType);
/*
* A map of registered sets
* key: set name
* value: configuration object containing
* key: sPath
* value: string path of the illustration set
* key: aSymbols
* value: array of the symbols names which the illustration set supports
* key: bIsPending
* value: boolean of whether or not the Illustration Set is loading
*/
var oSetRegistry = Object.create(null);
var aSymbolsInDOM = [], // Array with IDs of the symbols which are already in the DOM
oAssetRegistry = Object.create(null), // Object with IDs of the assets which are loaded or being loaded
oLoadedSymbols = Object.create(null), // Object which stores the loaded HTML raw data for a given asset for future reuse (might not be in the DOM yet)
oRequiredAssets = Object.create(null); // Object which stores the last required asset ID for a given instance
/**
* <code>IllustrationPool</code> loads the illustration assets (SVGs) via XMLHttpRequest requests.
*
* The successfully loaded data is kept in the DOM (div with ID <code>sap-illustration-pool</code>)
* in the <code>sap-ui-static</code> DOM element.
*
* To load a given asset, register its illustration set through the
* {@link sap.m.IllustrationPool.registerIllustrationSet registerIllustrationSet} API of <code>IllustrationPool</code>.
* The exception being the <code>sapIllus</code>, which is the default illustration set
* that is registered by default.
*
* The default behavior of <code>IllustrationPool</code> is to load/require an asset only
* when it's needed by using the {@link sap.m.IllustrationPool.loadAsset} API.
* When registering the new illustration set, you are given the option to load all of its assets.
*
* If some of the assets are not loaded initially, you can load the rest of them on
* a later state with the {@link sap.m.IllustrationPool.loadRestOfTheAssets} API.
*
* @namespace
* @since 1.98
* @public
* @alias sap.m.IllustrationPool
*/
var IllustrationPool = {};
/**
* PUBLIC METHODS
*/
/**
* Loads an SVG asset depending on the input asset ID.
*
* @param {string} sAssetId The string ID of the asset being loaded
* @param {string} sInstanceId the ID of the Illustration instance which is requiring the asset
* @param {string} sIdPrefix The prefix of the path of the asset being loaded. Used for loading assets from different collections. Used to store the asset ID in the DOM pool, so it can be distinguished from other assets with the same ID.
* @static
* @public
*/
IllustrationPool.loadAsset = function (sAssetId, sInstanceId, sIdPrefix) {
var sSet,
// the key under which the asset is stored in the registry
// it's the same as the asset ID, but with the prefix (if any, e.g. "v5/")
sRegistryKey;
sIdPrefix = sIdPrefix || "";
if (sAssetId === "") {
Log.error("ID of the asset can not be blank/empty.");
return;
}
sRegistryKey = sIdPrefix + sAssetId;
// if the the asset is required by an instance - cache its ID
if (sInstanceId) {
oRequiredAssets[sInstanceId] = sRegistryKey;
}
if (oAssetRegistry[sRegistryKey]) {
Log.info("The asset with ID '" + sAssetId + "' is either loaded or being loaded.");
// if the the asset is required by an instance and it's loaded - update the DOM pool
if (sInstanceId && oLoadedSymbols[sRegistryKey]) {
IllustrationPool._updateDOMPool();
}
return;
}
sSet = sAssetId.split("-")[0];
if (!oSetRegistry[sSet]) {
Log.error("The illustration set '" + sSet + "' is not registered. Please register it before requiring one of its assets.");
return;
}
oAssetRegistry[sRegistryKey] = true;
IllustrationPool._requireSVG(sSet, sAssetId, sInstanceId, sIdPrefix);
};
IllustrationPool._extractAssetMetadataConfig = function(sSet, sSize, sSymbol) {
var oMetadata = IllustrationPool.getIllustrationSetMetadata(sSet),
sIdPrefix = IllustrationPool._getThemePath(oMetadata),
oPathConfig = oMetadata && oMetadata.oPathSymbolsConfig && oMetadata.oPathSymbolsConfig[sIdPrefix] && oMetadata.oPathSymbolsConfig[sIdPrefix][sSymbol],
oRootConfig = oMetadata && oMetadata.oPathSymbolsConfig && oMetadata.oPathSymbolsConfig["root"] && oMetadata.oPathSymbolsConfig["root"][sSymbol],
oSymbolConfig = oPathConfig || oRootConfig, // if there is no path config, fallback to root config (if available)
sSizeSubstitute = sSize,
sSymbolSubstitute = sSymbol;
if (oSymbolConfig) {
sSizeSubstitute = IllustrationPool._getSizeSubstitute(oSymbolConfig, sSize);
sSymbolSubstitute = IllustrationPool._getSymbolSubstitute(oSymbolConfig, sSymbol);
}
return {
sIdPrefix: oPathConfig ? sIdPrefix : "",
sAssetId: `${sSet}-${sSizeSubstitute}-${sSymbolSubstitute}`
};
};
IllustrationPool._getSymbolSubstitute = function(oSymbolConfig, sSymbol) {
if (typeof oSymbolConfig === 'string') {
sSymbol = oSymbolConfig;
} else {
sSymbol = oSymbolConfig["symbolReplacement"] || sSymbol;
}
return sSymbol;
};
IllustrationPool._getSizeSubstitute = function(oSymbolConfig, sSize) {
if (typeof oSymbolConfig === 'object') {
sSize = oSymbolConfig["sizeReplacement"][sSize] || sSize;
}
return sSize;
};
/**
* Returns the metadata of an Illustration Set.
* The metadata contains the names of the symbols and the theme mappings.
* If the Illustration Set is not registered, an error is logged and null is returned.
* @public
* @param {string} sSet The name of the illustration set
* @returns {object} The metadata of the Illustration Set
* @since 1.116.0
**/
IllustrationPool.getIllustrationSetMetadata = function(sSet) {
if (!oSetRegistry[sSet]) {
Log.error("The illustration set '" + sSet + "' is not registered. Please register it before requiring one of its assets.");
return null;
}
return oSetRegistry[sSet];
};
/**
* Loads the rest of the SVG assets for a given illustration set.
*
* @param {string} sIllustrationSet The illustration set, the rest of the assets should be loaded for
* @static
* @public
*/
IllustrationPool.loadRestOfTheAssets = function(sIllustrationSet) {
var aSymbols;
if (!oSetRegistry[sIllustrationSet]) {
throw new Error("The illustration set '" + sIllustrationSet + "' is not registered. Please register it before requiring rest of its assets.");
}
aSymbols = oSetRegistry[sIllustrationSet].aSymbols;
if (Array.isArray(aSymbols)) {
aSymbols.forEach(function(sSymbol) {
IllustrationPool.loadAsset(sIllustrationSet + "-Dot-" + sSymbol);
IllustrationPool.loadAsset(sIllustrationSet + "-Spot-" + sSymbol);
IllustrationPool.loadAsset(sIllustrationSet + "-Dialog-" + sSymbol);
IllustrationPool.loadAsset(sIllustrationSet + "-Scene-" + sSymbol);
});
}
};
/**
* Registers an illustration set, which is needed before loading any of its assets.
*
* @param {object} oConfig object containing the name and the path of the Illustration Set
* @param {string} oConfig.setFamily Name of the Illustration Set
* @param {string} oConfig.setURI URL Path of the Illustration Set
* @param {boolean} bLoadAllResources whether or not all of the assets for the Illustration Set
* should be loaded once the metadata is loaded
* @param {array} aOptionalSymbols optional array containing the Illustration Set symbols
* @static
* @public
*/
IllustrationPool.registerIllustrationSet = async function(oConfig, bLoadAllResources, aOptionalSymbols) {
var sName = oConfig.setFamily,
sPath = oConfig.setURI;
if (oSetRegistry[sName]) {
if (oSetRegistry[sName].bIsPending) {
Log.warning("Illustration Set is currently being loaded.");
return await oSetRegistry[sName].oPromise;
} else {
Log.warning("Illustration Set already registered.");
}
return Promise.resolve();
}
// add trailing slash if necessary for more convenience
if (sPath.substr(sPath.length - 1) !== "/") {
sPath += "/";
}
oSetRegistry[sName] = Object.create(null);
oSetRegistry[sName].sPath = sPath;
oSetRegistry[sName].bIsPending = true;
await IllustrationPool._loadMetadata(sName, sPath, bLoadAllResources, aOptionalSymbols);
};
/**
* PRIVATE METHODS
*/
/**
* Adds an asset to the Illustration Pool's DOM element inner HTML.
*
* @param {string} sHTML string containing the raw HTML of an asset
* @param {string} sId string containing the ID of an asset
* @static
* @private
*/
IllustrationPool._addAssetToDOMPool = function(sHTML, sId) {
IllustrationPool._getDOMPool().insertAdjacentHTML("beforeend", sHTML);
if (sId) {
aSymbolsInDOM.push(sId);
}
};
/**
* Returns the DOM for the Illustration Pool.
* It also loads the mandatory patterns for the default Illustration Set once the DOM is created.
*
* @return {Object} DOM reference of the Illustration Pool
* @static
* @private
*/
IllustrationPool._getDOMPool = function() {
var oDOMPool = document.getElementById(SAP_ILLUSTRATION_POOL_ID);
if (oDOMPool === null) {
oDOMPool = document.createElement("div");
oDOMPool.id = SAP_ILLUSTRATION_POOL_ID;
oDOMPool.setAttribute("aria-hidden", "true");
StaticArea.getDomRef().appendChild(oDOMPool);
// Load the patterns for the default illustration set after the DOM Pool is created
IllustrationPool.loadAsset(SAP_ILLUSTRATION_SET_NAME + SAP_ILLUSTRATION_PATTERNS_NAME);
}
return oDOMPool;
};
/**
* Loads the metadata of an Illustration Set.
*
* @param {string} sName the name of the Illustration Set for which the metadata is being loaded
* @param {string} sPath the path of the Illustration Set for which the metadata is being loaded
* @param {boolean} bLoadAllResources whether or not all of the assets for the Illustration Set should be loaded
* @param {array} aOptionalSymbols optional array containing the Illustration Set symbols
* @return {Promise} Promise which resolves when the metadata is loaded
* @static
* @private
*/
IllustrationPool._loadMetadata = async function(sName, sPath, bLoadAllResources, aOptionalSymbols) {
var sMetadataPath = sPath + "metadata.json";
oSetRegistry[sName].oPromise = new Promise(function (fnResolve) {
jQuery.ajax(sMetadataPath, {
type: "GET",
dataType: "json",
success: function (oMetadataJSON) {
Log.info("Metadata for illustration set (" + sName + ") successfully loaded");
IllustrationPool._metadataLoaded(sName, oMetadataJSON, bLoadAllResources, aOptionalSymbols);
fnResolve(oMetadataJSON); // passing the json in the resolve for testing purposes
},
error: function (jqXHR, sStatus) {
if (sStatus !== "abort") { // log an error if it isn't aborted
Log.error("Metadata from: " + sMetadataPath + " file path could not be loaded");
delete oSetRegistry[sName];
fnResolve();
}
}
});
});
return await oSetRegistry[sName].oPromise;
};
/**
* Hook which is triggered when the metadata for an Illustration Set is loaded.
*
* @param {string} sName the name of the Illustration Set for which the metadata is loaded
* @param {object} oMetadataJSON the loaded metadata object
* @param {boolean} bLoadAllResources whether or not all of the assets for the Illustration Set should be loaded
* @param {array} aOptionalSymbols optional array containing the Illustration Set symbols
* @static
* @private
*/
IllustrationPool._metadataLoaded = function(sName, oMetadataJSON, bLoadAllResources, aOptionalSymbols) {
var aSymbols = aOptionalSymbols || oMetadataJSON.symbols,
oThemePathMap = oMetadataJSON.config && oMetadataJSON.config.themePath,
oPathSymbolsConfig = oMetadataJSON.pathSymbolsConfig,
bHasPatterns = oMetadataJSON.requireCustomPatterns;
oSetRegistry[sName].aSymbols = aSymbols;
oSetRegistry[sName].oThemePathMap = oThemePathMap;
oSetRegistry[sName].oPathSymbolsConfig = oPathSymbolsConfig;
// Load the patterns (if available) as soon as possible, since they can be used in any of the symbols.
if (bHasPatterns) {
IllustrationPool.loadAsset(sName + SAP_ILLUSTRATION_PATTERNS_NAME);
}
if (bLoadAllResources) {
IllustrationPool.loadRestOfTheAssets(sName);
}
oSetRegistry[sName].bIsPending = false;
oSetRegistry[sName].oPromise = null;
};
IllustrationPool._registerDefaultSet = async function () {
const oDefaultSet = {
setFamily: SAP_ILLUSTRATION_SET_NAME,
setURI: SAP_ILLUSTRATION_SET_PATH
};
await IllustrationPool.registerIllustrationSet(oDefaultSet, false, SAP_ILLUSTRATION_SET_SYMBOLS);
};
/**
* Removes an asset from the Illustration Pool's DOM element inner HTML.
*
* @param {string} sId string containing the ID of the asset which should be removed from the Illustration Pool's DOM
* @static
* @private
*/
IllustrationPool._removeAssetFromDOMPool = function(sId) {
var oDOMPool = document.getElementById(SAP_ILLUSTRATION_POOL_ID),
sExtractedAssetId = sId.split("/").pop(),
oAssetDOM;
if (oDOMPool !== null) {
oAssetDOM = document.getElementById(sExtractedAssetId);
if (oAssetDOM !== null) {
oDOMPool.removeChild(oAssetDOM);
}
}
aSymbolsInDOM.splice(aSymbolsInDOM.indexOf(sId), 1);
};
/**
* Loads an SVG asset.
*
* @param {string} sSet the name of the Illustration Set for which the asset is being loaded
* @param {string} sId the ID of the asset being loaded
* @param {string} sInstanceId the ID of the Illustration instance which is requiring the asset
* @param {string} sIdPrefix The prefix of the path of the asset being loaded. Used to store the asset ID in the DOM pool, so it can be distinguished from other assets with the same ID.
* @return {Promise} Promise which resolves when the SVG asset is loaded
* @static
* @private
*/
IllustrationPool._requireSVG = function(sSet, sId, sInstanceId, sIdPrefix) {
var sRegistryKey = sIdPrefix + sId;
sIdPrefix = sIdPrefix || "";
return new Promise(function (fnResolve) {
jQuery.ajax(oSetRegistry[sSet].sPath + sIdPrefix + sId + ".svg", {
type: "GET",
dataType: "html",
success: function (sHTML) {
// if asset is a symbol, and not a pattern
if (sId.indexOf(SAP_ILLUSTRATION_PATTERNS_NAME) === -1) {
// cache the loaded symbol
oLoadedSymbols[sRegistryKey] = sHTML;
// update the DOM if the asset is required by an instance
if (sInstanceId) {
IllustrationPool._updateDOMPool();
}
} else {
// add the pattern immediately
IllustrationPool._addAssetToDOMPool(sHTML);
}
fnResolve(sHTML);
},
error: function (jqXHR, sStatus) {
if (sStatus !== "abort") { // log an error if it isn't aborted
delete oAssetRegistry[sRegistryKey];
EventBus.getInstance().publish("sapMIllusPool-assetLdgFailed");
Log.error(sId + " asset could not be loaded");
fnResolve();
}
}
});
});
};
/**
* Updates the IllustrationPool's DOM node. Adds the currently required assets (if they are
* not already there) and removes the ones which are not needed.
*
* @static
* @private
*/
IllustrationPool._updateDOMPool = function() {
var oAssetsToKeep = Object.create(null),
oAssetsToAdd = Object.create(null),
sCurrAsset,
iCurrIndexInDOM;
for (var sCurrInstance in oRequiredAssets) {
sCurrAsset = oRequiredAssets[sCurrInstance];
iCurrIndexInDOM = aSymbolsInDOM.indexOf(sCurrAsset);
// if the asset is not already in the DOM, remember its ID and add it later, if it's loaded
if (iCurrIndexInDOM === -1) {
oAssetsToAdd[sCurrAsset] = true;
} else {
// if the asset is already in the DOM, remember its ID to help remove the not needed ones later
oAssetsToKeep[sCurrAsset] = true;
}
}
// remove the symbols which are not used and are not required
for (var i = 0; i < aSymbolsInDOM.length; i++) {
sCurrAsset = aSymbolsInDOM[i];
if (!oAssetsToKeep[sCurrAsset]) {
IllustrationPool._removeAssetFromDOMPool(sCurrAsset);
i--;
}
}
for (var sId in oAssetsToAdd) {
sCurrAsset = oLoadedSymbols[sId];
// add the missing required asset to the DOM only if it's loaded
if (sCurrAsset) {
IllustrationPool._addAssetToDOMPool(sCurrAsset, sId);
}
}
};
/**
* Gets the theme path prefix (if present) of the Illustration based on the current theme and the set's metadata.
* @param {Object} oMetadata The set's metadata
* @returns {string} the needed path prefix
* @private
*/
IllustrationPool._getThemePath = function (oMetadata) {
var sCurrentTheme = Theming.getTheme(),
oThemePathObj = oMetadata && oMetadata.oThemePathMap,
sPrefix = (oThemePathObj && oThemePathObj[sCurrentTheme]) || "";
return sPrefix;
};
return IllustrationPool;
}, /* bExport= */ true);