UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,146 lines (1,025 loc) 47.8 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([ '../base/ManagedObject', '../base/OwnStatics', './Element', './DeclarativeSupport', './XMLTemplateProcessor', 'sap/base/future', 'sap/base/Log', 'sap/base/util/LoaderExtensions', 'sap/base/util/merge', 'sap/ui/util/XMLHelper', 'sap/ui/core/Component', 'sap/ui/core/mvc/XMLProcessingMode' ], function( ManagedObject, OwnStatics, Element, DeclarativeSupport, XMLTemplateProcessor, future, Log, LoaderExtensions, merge, XMLHelper, Component, XMLProcessingMode ) { "use strict"; var mRegistry = {}, // the Fragment registry mTypes = {}; // the Fragment types registry, holding their implementations const { getCurrentOwnerId, runWithPreprocessors } = OwnStatics.get(ManagedObject); /** * @classdesc Fragments support the definition of light-weight stand-alone UI control trees. * This class acts as factory which returns the UI control tree defined inside the Fragments. When used within declarative Views, * the Fragment content is imported and seamlessly integrated into the View. * * Fragments are used similar as sap.ui.core.mvc.View, but Fragments do not have a Controller on their own (they may know one, though), * they are not a Control, they are not part of the UI tree and they have no representation in HTML. * By default, in contrast to declarative Views, they do not do anything to guarantee ID uniqueness. * * But like Views they can be defined in several Formats (XML and JavaScript; support for other types can be plugged in), * the declaration syntax is the same as in declarative Views and the name and location of the Fragment files is similar to Views. * Controller methods can also be referenced in the declarations, but as Fragments do not have their own controllers, * this requires the Fragments to be used within a View which does have a controller. * That controller is used, then. * * A JS Fragment can be defined in a dedicated module named "*.fragment.js". This module must return an object with a <code>createContent</code> method which has to return a control. * JS Fragments are also capable of asynchronously creating content. To do so, the <code>createContent</code> function must return a Promise * resolving with the content controls. * * <b>Example:</b> Defining a JS Fragment * <pre> * // e.g. module "my/sample/Button.fragment.js" * sap.ui.define(["sap/m/Button"], function(Button) { * return { * createContent: (oController) => { * const oButton = new Button({ * text: "Hello World" , * press: oController.doSomething * }); * return oButton; * } * }; * }); * </pre> * * <b>Example:</b> Defining a JS Fragment with a async 'createContent' method * <pre> * // e.g. module "my/sample/AsyncButton.fragment.js" * sap.ui.define(["sap/m/Button", "sap/base/i18n/ResourceBundle"], function(Button, ResourceBundle) { * return { * createContent: async (oController) => { * // loading a resource bundle async to retrieve button text * const myBundle = await ResourceBundle.create({ bundleName: "...", async: true }); * return new Button({ text: myBundle.getText("...") }); * } * }; * }); * </pre> * * Fragments can be instantiated with {@link sap.ui.core.Fragment.load Fragment.load} or the * {@link sap.ui.core.mvc.Controller.loadFragment loadFragment} function from a controller. * * Do not call the Fragment constructor directly! * * * Use-cases for Fragments are e.g.: * - Modularization of UIs without fragmenting the controller structure * - Re-use of UI parts * - 100%-declarative definition of Views * * @class * @extends sap.ui.base.ManagedObject * @author SAP SE * @version 1.147.0 * @public * @alias sap.ui.core.Fragment */ var Fragment = ManagedObject.extend("sap.ui.core.Fragment", { metadata: { properties: { /** * The Fragment type. */ type: 'string' }, specialSettings: { /** * Whether to load and parse the fragment asynchronous * @private */ async: { type: 'boolean', visibility: 'hidden' }, /* * Name of the fragment to load */ fragmentName : 'string', /* * Content of the fragment */ fragmentContent : 'any', /* * An enclosing view that contains this instance of the fragment (optional) */ containingView : { type: 'sap.ui.core.mvc.View', visibility: 'hidden' }, /* * A controller of a containing View that should be used by this fragment (optional) */ oController : { type: 'sap.ui.core.mvc.Controller', visibility: 'hidden' }, /* * The ID of this fragment (optional) */ sId : { type: 'sap.ui.core.ID', visibility: 'hidden' }, /** * The ID of the owner component (optional) */ sOwnerId : { type: 'sap.ui.core.ID', visibility: 'hidden' }, /** * @deprecated because only the "Sequential" processing mode will be supported * in the next major release * * The processing mode is not used by the Fragment itself. * It is only relevant for XMLViews nested within the Fragment. */ processingMode: { type: 'sap.ui.core.mvc.XMLProcessingMode', visibility: 'hidden' } } }, constructor: function(sId, mSettings) { ManagedObject.apply(this, arguments); /** * @deprecated because sync case is deprecated. */ if (!this._bAsync) { // When async, the fragment content is already passed to the constructor if (this._aContent && this._aContent.length == 1) { // in case of only one control, return it directly return this._aContent[0]; } else { return this._aContent; } } } }); /** * Registers a new Fragment type * * @param {string} sType the Fragment type. Types "XML", "HTML" and JS" are built-in and always available. * @param {object} oFragmentImpl an object having the properties "init" and "load". * @param {function} oFragmentImpl.init Called on Fragment instantiation with the settings map as argument. Function needs to return a promise which resolves with sap.ui.core.Control|sap.ui.core.Control[] * @param {function} oFragmentImpl.load Called to load the fragment content. Must return a Promise which resolves with the loaded resource. This resource is passed as 'fragmentContent' to the init() function via a parameter object. * @public */ Fragment.registerType = function(sType, oFragmentImpl) { if (typeof (sType) !== "string") { future.errorThrows("Invalid non-string Fragment type: '" + sType + "'.", { suffix: "It will be ignored." }); return; } if (mTypes[sType]) { Log.warning("sap.ui.core.Fragment.registerType(): Fragment type '" + sType + "' is already defined. Overriding this type now!"); } mTypes[sType] = oFragmentImpl; }; Fragment.prototype._initCompositeSupport = function(mSettings) { if (!mSettings) { throw new Error("Settings must be set"); } if (!(mSettings.fragmentName || mSettings.fragmentContent)) { throw new Error("Please provide a fragment name"); } if (mSettings.oController) { this.oController = mSettings.oController; } /** * @deprecated because sync case is deprecated. */ this._bAsync = mSettings.async || false; // remember the ID which has been explicitly given in the factory function this._sExplicitId = mSettings.sId || mSettings.id; // remember the name of this Fragment this._sFragmentName = mSettings.fragmentName; // if the containing view (or fragment) has a scoped runWithOwner function we need to propagate this to the nested Fragment (only for async case) this.fnScopedRunWithOwner = mSettings.containingView && mSettings.containingView.fnScopedRunWithOwner; if (!this.fnScopedRunWithOwner && this._sOwnerId) { var oOwnerComponent = Component.getComponentById(this._sOwnerId); this.fnScopedRunWithOwner = function(fnCallbackToBeScoped) { return oOwnerComponent.runAsOwner(fnCallbackToBeScoped); }; } var oFragmentImpl = Fragment.getType(mSettings.type); if (oFragmentImpl) { this._pContentPromise = oFragmentImpl.init.apply(this, [mSettings]); if (!this._pContentPromise) { // TODO Remove this if after sap.fe changed there custom fragment implementation this._pContentPromise = Promise.resolve(this._aContent); } } else { // Fragment type not found throw new Error("No type for the fragment has been specified: " + mSettings.type); } }; /** * Returns the name of the fragment. * * @returns {string} the fragment name * @private */ Fragment.prototype.getFragmentName = function() { // required for the parser to lookup customizing configuration return this._sFragmentName; }; /** * * @returns {sap.ui.core.mvc.Controller|null} the Controller connected to this Fragment, or <code>null</code> * @private */ Fragment.prototype.getController = function() { // required for the parsers to find the specified Controller methods return this.oController; }; /** * Returns an Element/Control by its ID in the context of the Fragment with the given ID * * @param {string} sFragmentId ID of the Fragment from which to retrieve the Control * @param {string} sId ID of the Element/Control to retrieve * * @returns {sap.ui.core.Element|undefined} Element by its ID and Fragment ID * @public * @static */ Fragment.byId = function(sFragmentId, sId) { if (!(typeof (sFragmentId) === "string" && typeof (sId) === "string")) { future.errorThrows("sap.ui.core.Fragment.byId: two strings must be given as parameters, but are: " + sFragmentId + " and " + sId); return undefined; } return Element.getElementById(sFragmentId + "--" + sId); }; /** * Returns the ID which a Control with the given ID in the context of the Fragment with the given ID would have. * * @param {string} sFragmentId ID of the Fragment for which to calculate the Control ID * @param {string} sId Fragment-local ID of the Control to calculate the ID for * * @returns {string} the prefixed ID * @public * @static */ Fragment.createId = function(sFragmentId, sId) { if (!(typeof (sFragmentId) === "string" && typeof (sId) === "string")) { future.errorThrows("sap.ui.core.Fragment.createId: two strings must be given as parameters, but are: " + sFragmentId + " and " + sId); return undefined; } return sFragmentId + "--" + sId; }; /** * Creates an id for an Element prefixed with the Fragment id. * This method only adds a prefix when an ID was explicitly given when instantiating this Fragment. * If the ID was generated, it returns the unmodified given ID. * * @param {string} sId The given id * @return {string} prefixed id The prefixed id or the given id */ Fragment.prototype.createId = function(sId) { var id = this._sExplicitId ? this._sExplicitId + "--" + sId : sId; // no ID Prefixing by Fragments! This is called by the template parsers, but only if there is not a View which defines the prefix. if (this._oContainingView && this._oContainingView != this) { // if Fragment ID is added to the control ID and Fragment ID already contains the View prefix, the View prefix does not need to be added again // (this will now be checked inside the createId function already!) id = this._oContainingView.createId(id); } return id; }; /** * Always return true in case of fragment * * @returns {boolean} <code>true</code> * @private */ Fragment.prototype.isSubView = function(){ return true; }; // ### Factory functions ### /** * Loads and instantiates a fragment. * The fragment object itself is not an entity that has any further significance beyond this factory function. * * To instantiate a fragment that is already defined separately, call: * <pre> * sap.ui.fragment(sName, sType, oController?); * </pre> * * Advanced usage to give further configuration options: * <pre> * sap.ui.fragment(oFragmentConfig, oController?); * </pre> * In addition to <code>id</code> and <code>type</code>, the <code>oFragmentConfig</code> object * can have either a <code>fragmentName</code> or a <code>fragmentContent</code> property, but not both. * * To define a fragment ID, which can be used as a prefix for the created control IDs, * you must use either the above advanced version with an <code>id</code> or one of the typed factory functions * like {@link sap.ui.xmlfragment} or {@link sap.ui.jsfragment}. * * A fragment type must be given in all cases. The fragment types <code>"XML"</code>, <code>"JS"</code>, and <code>"HTML"</code> (type <code>"HTML"</code> is deprecated) * are available by default. Additional fragment types can be implemented and added using the {@link sap.ui.core.Fragment.registerType} function. * * Custom fragment types can support further properties. Any given properties will be forwarded to the fragment implementation. * * The optional <code>oController</code> can be either the controller of an enclosing view, * a new controller instance, or a simple object with the necessary methods attached. * Note that a fragment has no runtime representation besides its contained controls. Therefore, there is no API to retrieve the controller from the return value. * Note also that fragments may require a controller to be given and certain methods to be available. * * <b>Note:</b> * In case you are embedding a Fragment into an existing View, please also have a look at the * {@link sap.ui.core.mvc.Controller.loadFragment loadFragment} factory for a closer coupling to the corresponding Controller instance. * * @param {string|object} vName * resource name of the fragment module in dot notation without the <code>.fragment.<i>&lt;typeExtension></i></code> suffix from the file name. * Alternatively, a configuration object as specified below * @param {string} [vName.id] * optional ID of the created fragment * @param {string|object} [vName.fragmentContent] * definition of the fragment content that will be used instead of loading the content from a separate file. * The type of this property depends on the fragment type. For example, it could be a string for XML fragments or an object for JS fragments * @param {string} [vName.fragmentName] * recource name of the fragment module as specified above * @param {string} vName.type * type of the fragment, for example, <code>"XML"</code>, <code>"JS"</code>, <code>"HTML"</code>, or any other type that has been implemented additionally * @param {string|sap.ui.core.mvc.Controller|object} vType * type of the fragment as specified by <code>vName.type</code> or, in the advanced usage case, an optional <code>oController</code> * @param {sap.ui.core.mvc.Controller|object} [oController] * controller object to be used for methods or event handlers referenced in the fragment * @public * @static * @deprecated As of version 1.58. Refer to {@link topic:04129b2798c447368f4c8922c3c33cd7 Instantiation of Fragments}. * @returns {sap.ui.core.Control|sap.ui.core.Control[]} the instantiated root control(s) from the fragment content * @ui5-global-only */ sap.ui.fragment = function (vName, vType, oController) { var sFragmentType; if (typeof vType === "string") { sFragmentType = vType.toLowerCase(); } else if (typeof vName === "object" && typeof vName.type === "string") { sFragmentType = vName.type.toLowerCase(); } else { sFragmentType = ""; } Log.info("Do not use deprecated factory function 'sap.ui." + sFragmentType + "fragment'.", "sap.ui." + sFragmentType + "fragment", null, function () { var oSupportInfo = { type: "sap.ui." + sFragmentType + "fragment", name: ( vName.fragmentName || vName ) + ".fragment." + sFragmentType }; if (vName.fragmentContent) { oSupportInfo.content = vName.fragmentContent; oSupportInfo.name = ""; } return oSupportInfo; }); if ((typeof vName === "object" && vName.fragmentName?.startsWith("module:")) || (typeof vName === "string" && vName.startsWith("module:"))) { throw new Error(`sap.ui.fragment(): module name syntax '${vName.fragmentName || vName}' is not supported.`); } return fragmentFactory(vName, vType, oController); }; /** * @see sap.ui.core.Fragment.load * * @private * @param {string|object} vName The fragment name or the fragment config * @param {string|sap.ui.core.mvc.Controller} vType The type of the fragment or the controller * @param {sap.ui.core.mvc.Controller|object} oController the Controller or object which should be used by the controls in the Fragment. * @returns {Promise<sap.ui.core.Control|sap.ui.core.Control[]>|sap.ui.core.Fragment} If fragment is created asynchronoulsy * a Promise is returned which resolves with the resulting {sap.ui.core.Control|sap.ui.core.Control[]} * after fragment parsing and instantiation. * If the fragment is created synchronoulsy the newly created fragment instance is returned */ function fragmentFactory(vName, vType, oController) { var mSettings = {}; if (typeof (vName) === "string") { // normal call mSettings.fragmentName = vName; mSettings.oController = oController; mSettings.type = vType; } else if (typeof (vName) === "object") { // advanced call with config object mSettings = vName; // pass all config parameters to the implementation // mSettings.async could be undefined when fragmentFactory is triggered by old sap.ui.fragment api mSettings.async = mSettings.async === true ? mSettings.async : false; if (vType) { // second parameter "vType" is in this case the optional Controller mSettings.oController = vType; } if (mSettings.async) { var fnCreateInstance = function () { // owner-id is either available because the async factory was called in a sync block // or: the containing view carries the owner id for us var sOwnerId = mSettings.sOwnerId || mSettings.containingView && mSettings.containingView._sOwnerId; var oOwnerComponent = Component.getComponentById(sOwnerId); if (oOwnerComponent) { return oOwnerComponent.runAsOwner(function () { return new Fragment(mSettings); }); } return new Fragment(mSettings); }; var oType = Fragment.getType(mSettings.type); if (mSettings.fragmentName && mSettings.fragmentContent) { delete mSettings.fragmentName; } if (mSettings.fragmentName && typeof oType.load == "function") { return new Promise(function(resolve, reject) { oType.load(mSettings).then(function (vContent) { mSettings.fragmentContent = vContent; resolve(fnCreateInstance()); }).catch(function (oError) { reject(oError); }); }); } else { // in case there is no 'fragmentName' but a 'definition' for the fragment provided or in case there is no load function available (sync use case) return Promise.resolve(fnCreateInstance()); } } } else { Log.error("sap.ui.fragment() must be called with Fragment name or config object as first parameter, but is: " + vName); } return new Fragment(mSettings); } /** * Loads and instantiates a fragment. * Also refer to {@link topic:04129b2798c447368f4c8922c3c33cd7 Instantiation of Fragments}. * * The fragment object itself is not an entity that has any further significance beyond this factory function. * * A Promise is returned, which resolves with the fragment's content. * * The Fragment types <code>"XML"</code>, <code>"JS"</code>, and <code>"HTML"</code> (type <code>"HTML"</code> is deprecated) are available by default. * Additional Fragment types can be implemented and added using the {@link sap.ui.core.Fragment.registerType} function. * * Further properties may be supported by future or custom Fragment types. Any given properties * will be forwarded to the Fragment implementation. * * If no fixed ID is given, the fragment ID is generated. In any case, the fragment ID will be used as prefix for the IDs of * all contained controls. * * <b>Note:</b> * In case you are embedding a Fragment into an existing View, please also have a look at the * {@link sap.ui.core.mvc.Controller.loadFragment loadFragment} factory for a closer coupling to the corresponding Controller instance. * * @example <caption>Loading an XML fragment (default type)</caption> * sap.ui.require(["sap/ui/core/Fragment"], async (Fragment) => { * const myFrag = await Fragment.load({ * name: "my.useful.VerySimpleUiPart" * }); * }); * * @example <caption>Creating an XML fragment</caption> * sap.ui.require(["sap/ui/core/Fragment"], async (Fragment) => { * const myFrag = await Fragment.load({ * type: "XML", * definition: '&lt;Button xmlns=&quot;sap.m&quot; id=&quot;xmlfragbtn&quot; text=&quot;This is an XML Fragment&quot; press=&quot;doSomething&quot;&gt;&lt;/Button&gt;' * }); * }); * * @example <caption>Creating a JS fragment</caption> * sap.ui.require(["sap/ui/core/Fragment"], async (Fragment) => { * const myFrag = await Fragment.load({ * name: "module:my/sample/AsyncButton", * type: "JS", * }); * }); * * @example <caption>Creating an HTML fragment (deprecated)</caption> * sap.ui.require(["sap/ui/core/Fragment"], async (Fragment) => { * const myFrag = await Fragment.load({ * type: "HTML", * definition: '&lt;div id=&quot;htmlfragbtn&quot; data-sap-ui-type=&quot;sap.m.Button&quot; data-text=&quot;This is an HTML Fragment&quot;&gt;&lt;/div&gt;' * }); * }); * * <b>Note:</b> If the fragment contains <code>ExtensionPoint</code>s, you have to pass the parameter <code>containingView</code>. * The containing view should be the View instance into which the fragment content will be inserted manually. * * @param {object} mOptions options map * @param {string} [mOptions.name] Must be provided if no <code>definition</code> parameter is given. The fragment name must correspond to an XML fragment which * can be loaded via the module system and must contain the fragment definition. It can be specified either in dot notation * (fragmentName + suffix <code>.fragment.<i>&lt;typeExtension></i></code>) or, for JS fragments, in module name syntax (<code>module:my/sample/AsyncButton</code>). * If <code>mOptions.controller</code> is supplied, the (event handler) methods referenced in the fragment will be called on that controller. * Note that fragments may require a controller to be given and certain methods to be implemented by it. * @param {string} [mOptions.type=XML] the fragment type, e.g. <code>"XML"</code>, <code>"JS"</code>, or <code>"HTML"</code> (type <code>"HTML"</code> is deprecated). Default is <code>"XML"</code>. * If the fragment name is given in module name syntax (e.g., <code>module:my/sample/AsyncButton</code>) the type must be omitted. * @param {string} [mOptions.definition] definition of the fragment content. When this property is supplied, the <code>name</code> parameter must not be used. If both are supplied, the definition has priority. * Please see the above example on how to use the <code>definition</code> parameter. * @param {string} [mOptions.id] the ID of the fragment * @param {sap.ui.core.mvc.Controller|object} [mOptions.controller] the controller or object which should be used by the controls in the fragment. * Note that some fragments may not need a controller while others may need one and certain methods to be implemented by it. * @param {sap.ui.core.mvc.View} [mOptions.containingView] The view containing the fragment content. If the fragment content contains <code>ExtensionPoint</code>s, this parameter must be given. * @public * @static * @since 1.58 * @returns {Promise<sap.ui.core.Control|sap.ui.core.Control[]>} a <code>Promise</code> resolving with the resulting control (array) after fragment parsing and instantiation * @throws {TypeError} If the fragment name is given in module name syntax and a fragment type is provided. */ Fragment.load = function(mOptions) { var mParameters = Object.assign({}, mOptions); if (mParameters.name && mParameters.definition) { Log.error("The properties 'name' and 'definition' shouldn't be provided at the same time. The fragment definition will be used instead of the name. Fragment name was: " + mParameters.name); delete mParameters.name; } // Sanity check and default fragment type handling if (mParameters.name?.startsWith("module:")) { if (mParameters.type) { throw new TypeError(`Invalid arguments: If the fragment name is given in module name syntax the type must be omitted. Found type: '${mParameters.type}'.`); } else { mParameters.type = "JS"; } } else { mParameters.type ??= "XML"; } mParameters.async = true; /** * @deprecated because the 'Sequential' Mode is used by default and it's the only mode that will be supported * in the next major release */ mParameters.processingMode = mParameters.processingMode || XMLProcessingMode.Sequential; // map new parameter names to classic API, delete new names to avoid assertion failures mParameters.fragmentName = mParameters.fragmentName || mParameters.name; mParameters.fragmentContent = mParameters.fragmentContent || mParameters.definition; mParameters.oController = mParameters.controller; mParameters.sOwnerId = getCurrentOwnerId(); delete mParameters.name; delete mParameters.definition; delete mParameters.controller; var pFragment = fragmentFactory(mParameters); return pFragment.then(function(oFragment) { return oFragment._pContentPromise; }); }; /** * Get the implementation of the init and the load function for the requested fragment type. * @param {string} sType Name of the fragment type * @returns {object} returns an object containing the init and the load function of requested fragment type * @since 1.86 * @static * @private * @ui5-restricted sap.fe */ Fragment.getType = function (sType) { return mTypes[sType]; }; /** * Loads and instantiates an XML-based fragment. * * To instantiate a fragment that is already defined separately, call: * <pre> * sap.ui.xmlfragment(sId?, sFragmentName, oController?); * </pre> * * Advanced usage: * <pre> * sap.ui.xmlfragment(oFragmentConfig, oController?); * </pre> * In addition to an <code>id</code>, the <code>oFragmentConfig</code> object can have either a <code>fragmentName</code> * or a <code>fragmentContent</code> property, but not both. * * @param {string|object} [vId] * ID of the created fragment which will be used as prefix to all contained control IDs. * If the first argument is not an ID, it must be either the fragment name (<code>sFragmentName</code>) * or a configuration object (<code>oFragmentConfig</code>) as specified below * @param {string} [vId.id] * ID of the created fragment which will be used as prefix to all contained control IDs * @param {string} [vId.fragmentContent] * definition of the fragment content as an XML string that will be used * instead of loading the content from a separate <code>.fragment.xml</code> file. * When this property is given, any given fragment name is ignored * @param {string} [vId.fragmentName] * resource name of the fragment module in dot notation without the <code>.fragment.xml</code> suffix from the file name * @param {string|sap.ui.core.mvc.Controller|object} vFragment * resource name of the fragment module as specified by <code>vId.fragmentName</code> or, * in the advanced usage case, an optional <code>oController</code> * @param {sap.ui.core.mvc.Controller|object} [oController] * controller object to be used for methods or event handlers. Can be either the controller of an * enclosing view, a new controller instance, or a simple object with the necessary methods attached. * Note that a fragment has no runtime representation besides its contained controls. Therefore, there is * no API to retrieve the controller from the return value. * Note also that fragments may require a controller to be given and certain methods to be available * @public * @static * @deprecated As of version 1.58. Refer to {@link topic:04129b2798c447368f4c8922c3c33cd7 Instantiation of Fragments}. * @return {sap.ui.core.Control|sap.ui.core.Control[]} the instantiated root control(s) from the fragment content * @ui5-global-only */ sap.ui.xmlfragment = function(vId, vFragment, oController) { if (typeof (vId) === "string") { // basic call if (typeof (vFragment) === "string") { // with ID return sap.ui.fragment({fragmentName: vFragment, sId: vId, type: "XML"}, oController); // legacy-relevant } else { // no ID, sId is actually the name and vFragment the optional Controller return sap.ui.fragment(vId, "XML", vFragment); // legacy-relevant } } else { // advanced call vId.type = "XML"; // second parameter "vFragment" is the optional Controller return sap.ui.fragment(vId, vFragment); // legacy-relevant } }; /** * Defines <strong>or</strong> instantiates a JS-based fragment. * * To define a JS fragment, call: * <pre> * sap.ui.jsfragment(sName, oFragmentDefinition); * </pre> * * To instantiate a JS fragment that is already defined, call: * <pre> * sap.ui.jsfragment(sId?, sFragmentName, oController?); * </pre> * Advanced usage: * <pre> * sap.ui.jsfragment(oFragmentConfig, oController?); * </pre> * * * @param {string|object} vName * when defining a fragment: name of the fragment.<br> * When loading a fragment: fragment ID (optional), fragment name, or configuration object as specified below * @param {string} [vName.id] * ID of the newly created fragment which <i>can</i> be used as a prefix * when creating the IDs in the JS fragment content. Even if an <code>id</code> is given, some JS fragments may choose * not to use the ID prefixing, for example, in order to prevent the fragment from being instantiated multiple times * within the lifecycle of the existing fragment * @param {string} vName.fragmentName * resource name of the fragment module in dot notation without the <code>.fragment.js</code> suffix from the file name. * When no fragment has been defined with that name, the name will be converted * to a path by replacing dots with slashes and appending <code>.fragment.js</code>. The corresponding resource to load * is expected to have a fragment defined with the same <code>fragmentName</code> * @param {object|string} vFragmentDefinition * when defining a fragment: object holding at least the <code>createContent(oController?)</code> * method that returns an instance of <code>sap.ui.core.Control</code> or an array thereof.<br> * When loading a fragment and the first argument is an ID: the <code>fragmentName</code> * @param {sap.ui.core.mvc.Controller|object} [oController] * controller object to be used for methods or event handlers. Can be either the controller of an * enclosing view, a new controller instance, or a simple object with the necessary methods attached. * Note that a fragment has no runtime representation besides its contained controls. Therefore, there is * no API to retrieve the controller from the return value * @public * @static * @deprecated As of version 1.58. Refer to {@link topic:04129b2798c447368f4c8922c3c33cd7 Instantiation of Fragments}. * @return {sap.ui.core.Control|sap.ui.core.Control[]} the instantiated root control(s) from the fragment content * @ui5-global-only */ sap.ui.jsfragment = function(vName, vFragmentDefinition, oController) { // definition of a JS Fragment if (typeof vName === "string" && typeof vFragmentDefinition === "object") { if (vFragmentDefinition.createContent) { // Fragment DEFINITION mRegistry[vName] = vFragmentDefinition; sap.ui.loader._.declareModule(vName.replace(/\./g, "/") + ".fragment.js"); // TODO: return value? } else { // plain instantiation: name[+oController] return sap.ui.fragment(vName, "JS", vFragmentDefinition); // legacy-relevant } } else if (typeof vName === "string" && vFragmentDefinition === undefined) { // plain instantiation: name only return sap.ui.fragment(vName, "JS"); // legacy-relevant } else if (typeof vName === "object") { // advanced mode: oConfig+[oController] vName.type = "JS"; return sap.ui.fragment(vName, vFragmentDefinition); // legacy-relevant } else if (arguments.length >= 3) { // must be plain instantiation mode: ID+Name[+Controller] return sap.ui.fragment({id: vName, fragmentName: vFragmentDefinition, type: "JS"}, oController); // legacy-relevant } else { Log.error("sap.ui.jsfragment() was called with wrong parameter set: " + vName + " + " + vFragmentDefinition); } }; /** * Loads and instantiates an HTML-based fragment. * * To instantiate a fragment that is already defined separately, call: * <pre> * sap.ui.htmlfragment(sId?, sFragmentName, oController?); * </pre> * * Advanced usage: * <pre> * sap.ui.htmlfragment(oFragmentConfig, oController?); * </pre> * In addition to an <code>id</code>, the <code>oFragmentConfig</code> object can have either a <code>fragmentName</code> * or a <code>fragmentContent</code> property, but not both. * * @param {string|object} [vId] * ID of the created fragment which will be used as prefix to all contained control IDs. * If the first argument is not an ID, it must be either the fragment name (<code>sFragmentName</code>) * or a configuration object (<code>oFragmentConfig</code>) as specified below * @param {string} [vId.id] * ID of the created fragment which will be used as prefix to all contained control IDs * @param {string} [vId.fragmentContent] * definition of the fragment content as an HTML string that will be used * instead of loading the content from a separate <code>.fragment.html</code> file. * When this property is given, any given fragment name is ignored * @param {string} [vId.fragmentName] * resource name of the fragment module in dot notation without the <code>.fragment.html</code> suffix from the file name * @param {string|sap.ui.core.mvc.Controller|object} vFragment * resource name of the fragment module as specified by <code>vId.fragmentName</code> or, * in the advanced usage case, an optional <code>oController</code> * @param {sap.ui.core.mvc.Controller|object} [oController] * controller object to be used for methods or event handlers. Can be either the controller of an * enclosing view, a new controller instance, or a simple object with the necessary methods attached. * Note that a fragment has no runtime representation besides its contained controls. Therefore, there is * no API to retrieve the controller from the return value. * Note also that fragments may require a controller to be given and certain methods to be available * @public * @static * @deprecated As of version 1.58. Additionally, use of fragments based on type <code>"HTML"</code> is deprecated since 1.108. * If you need declarative fragments, use XML fragments instead. Refer to {@link topic:04129b2798c447368f4c8922c3c33cd7 Instantiation of Fragments}. * @return {sap.ui.core.Control|sap.ui.core.Control[]} the instantiated root control(s) from the fragment content * @ui5-global-only */ sap.ui.htmlfragment = function(vId, vFragment, oController) { if (typeof (vId) === "string") { // basic call if (typeof (vFragment) === "string") { // with ID return sap.ui.fragment({fragmentName: vFragment, sId: vId, type: "HTML"}, oController); // legacy-relevant } else { // no ID, vId is actually the name and vFragment the optional Controller return sap.ui.fragment(vId, "HTML", vFragment); // legacy-relevant } } else { // advanced call vId.type = "HTML"; // second parameter "vFragment" is the optional Controller return sap.ui.fragment(vId, vFragment); // legacy-relevant } }; // ### FRAGMENT TYPES ### // ### XML Fragments ### Fragment.registerType("XML" , { load: function(mSettings) { // type "XML" return XMLTemplateProcessor.loadTemplatePromise(mSettings.fragmentName, "fragment").then(function(documentElement) { return documentElement; }); }, init: function(mSettings) { this._aContent = []; /** * @ui5-transform-hint replace-local true */ const bFragmentContent = !!mSettings.fragmentContent; if (bFragmentContent) { // use specified content or load the content definition if (typeof (mSettings.fragmentContent) === "string") { this._xContent = XMLHelper.parse(mSettings.fragmentContent).documentElement; } else { this._xContent = mSettings.fragmentContent; } } else { Log.warning("Synchronous loading of fragment, due to Fragment.init() call for '" + mSettings.fragmentName + "'. Use 'sap/ui/core/Fragment' module with Fragment.load() instead.", "SyncXHR", null, function() { return { type: "SyncXHR", name: "Fragment" }; }); this._xContent = XMLTemplateProcessor.loadTemplate(mSettings.fragmentName, "fragment"); } this._oContainingView = this._sExplicitId ? this : (mSettings.containingView || this); if ((this._oContainingView === this) ) { this._oContainingView.oController = (mSettings.containingView && mSettings.containingView.oController) || mSettings.oController; } /** * @deprecated because the 'Sequential' Mode is used by default and it's the only mode that will be supported * in the next major release * // If given, processingMode will be passed down to nested subviews in XMLTemplateProcessor */ this._sProcessingMode = mSettings.processingMode; // take the settings preprocessor from the containing view (if any) var fnSettingsPreprocessor = this._oContainingView._fnSettingsPreprocessor; // similar to the XMLView we need to have a scoped runWithPreprocessors function var oParseConfig = { fnRunWithPreprocessor: function(fn) { return runWithPreprocessors(fn, { settings: fnSettingsPreprocessor }); } }; // finally trigger the actual XML processing and control creation // IMPORTANT: // this call can be triggered with both "async = true" and "async = false" // In case of sync processing, the XMLTemplateProcessor makes sure to only use SyncPromises. /** * @ui5-transform-hint replace-local true */ const bAsync = this._bAsync; var pContentPromise = XMLTemplateProcessor.parseTemplatePromise(this._xContent, this, bAsync, oParseConfig).then(function(aContent) { this._aContent = aContent; /* * If content was parsed and an objectBinding at the fragment was defined * the objectBinding must be forwarded to the created controls */ if (this._aContent && this._aContent.length && mSettings.objectBindings) { this._aContent.forEach(function (oContent, iIndex) { if (oContent instanceof Element) { for (var sModelName in mSettings.objectBindings) { oContent.bindObject(mSettings.objectBindings[sModelName]); } } }); } return this._aContent.length > 1 ? this._aContent : this._aContent[0]; }.bind(this)); if (!bAsync) { try { // in sync case we must get a SyncPromise and need to unwrap for error logging pContentPromise.unwrap(); } catch (e) { future.errorThrows(this.getMetadata().getName() + ": An Error occured during XML processing of '" + (mSettings.fragmentName || mSettings.fragmentContent) + "' with id '" + this.getId() + "'", { cause: e }); } } return pContentPromise; } }); // ### JS Fragments ### Fragment.registerType("JS", { load: function(mSettings) { let sFragmentPath; // Handle module: syntax if (mSettings.fragmentName.startsWith("module:")) { sFragmentPath = mSettings.fragmentName.substring(7); } else { // Handle dot-separated syntax sFragmentPath = mSettings.fragmentName.replace(/\./g, "/") + ".fragment"; } return new Promise(function(resolve, reject) { sap.ui.require([sFragmentPath], function(content) { resolve(content); }, reject); }); }, init: function(mSettings) { this._aContent = []; if (mSettings.fragmentContent) { // Mixin fragmentContent into Fragment instance merge(this, mSettings.fragmentContent); } else { /** * @deprecated */ (() => { /*** require fragment definition if not yet done... ***/ if (!mRegistry[mSettings.fragmentName]) { sap.ui.requireSync(mSettings.fragmentName.replace(/\./g, "/") + ".fragment"); // legacy-relevant: Sync path } })(); /*** Step 2: merge() ***/ merge(this, mRegistry[mSettings.fragmentName]); } this._oContainingView = mSettings.containingView || this; // unset any preprocessors (e.g. from an enclosing JSON view) return runWithPreprocessors(function() { var vContent; if (this.fnScopedRunWithOwner) { this.fnScopedRunWithOwner(function () { vContent = this.createContent(mSettings.oController || this._oContainingView.oController); }.bind(this)); } else { vContent = this.createContent(mSettings.oController || this._oContainingView.oController); } // createContent might return a Promise too if (vContent instanceof Promise) { return vContent.then(function(aContent) { this._aContent = this._aContent.concat(aContent); return this._aContent.length > 1 ? this._aContent : this._aContent[0]; }.bind(this)); } else { // vContent is not a Promise, but a synchronously processed array of controls return new Promise(function (resolve, reject) { this._aContent = this._aContent.concat(vContent); resolve(this._aContent.length > 1 ? this._aContent : this._aContent[0]); }.bind(this)); } }.bind(this), { settings: this._oContainingView._fnSettingsPreprocessor }); } }); // ### HTML Fragments ### /** * @deprecated As of version 1.108, together with the HTMLView. If you need declarative fragments, * use XML fragments instead. */ (function() { /** * The template cache. Templates are only loaded once. * * @private * @static */ var _mHTMLTemplates = {}; /** * Loads and returns a template for the given template name. Templates are only loaded once. * * @param {string} sTemplateName The name of the template * @return {string} the template data * @private */ var _getHTMLTemplate = function(sTemplateName) { var sUrl = sap.ui.require.toUrl(sTemplateName.replace(/\./g, "/")) + ".fragment.html"; var sHTML = _mHTMLTemplates[sUrl]; var sResourceName; if (!sHTML) { sResourceName = sTemplateName.replace(/\./g, "/") + ".fragment.html"; sHTML = LoaderExtensions.loadResource(sResourceName); // TODO discuss // a) why caching at all (more precise: why for HTML fragment although we refused to do it for other view/fragment types - risk of a memory leak!) // b) why cached via URL instead of via name? Any special scenario in mind? _mHTMLTemplates[sUrl] = sHTML; } return sHTML; }; Fragment.registerType("HTML", { load: function(mSettings) { var sFragmentPath = mSettings.fragmentName.replace(/\./g, "/") + ".fragment"; return LoaderExtensions.loadResource(sFragmentPath + ".html", {async: true}).then(function(oContent) { return oContent; }); }, init: function(mSettings) { // DeclarativeSupport automatically uses set/getContent, but Fragment should not have such an aggregation and should not be parent of any control // FIXME: the other aggregation methods are not implemented. They are currently not used, but who knows... this._aContent = []; this.getContent = function() { return this._aContent; }; this.addContent = function(oControl) { this._aContent.push(oControl); }; this._oContainingView = mSettings.containingView || this; // processing mode is propagated to subviews/fragments this._sProcessingMode = mSettings.processingMode; var vHTML = mSettings.fragmentContent || _getHTMLTemplate(mSettings.fragmentName); this._oTemplate = document.createElement("div"); if (typeof vHTML === "string") { this._oTemplate.innerHTML = vHTML; } else { var oNodeList = vHTML; var oDocumentFragment = document.createDocumentFragment(); for (var i = 0; i < oNodeList.length; i++) { oDocumentFragment.appendChild(oNodeList.item(i)); } this._oTemplate.appendChild(oDocumentFragment); } var oMetaElement = this._oTemplate.getElementsByTagName("template")[0]; var oProperties = this.getMetadata().getAllProperties(); if (oMetaElement) { var that = this; var aAttributes = oMetaElement.getAttributeNames(); for (var i = 0; i < aAttributes.length - 1; i++) { var sAttributeName = aAttributes[i]; var sSettingName = DeclarativeSupport.convertAttributeToSettingName(sAttributeName, that.getId()); var sValue = oMetaElement.getAttribute(sAttributeName); var oProperty = oProperties[sSettingName]; if (!mSettings[sSettingName]) { if (oProperty) { mSettings[sSettingName] = DeclarativeSupport.convertValueToType(DeclarativeSupport.getPropertyDataType(oProperty),sValue); } else if (sap.ui.core.mvc.HTMLView._mAllowedSettings[sSettingName]) { mSettings[sSettingName] = sValue; } } } this._oTemplate = oMetaElement; } // This is a fix for browsers that support web components if (this._oTemplate.content) { var oFragment = this._oTemplate.content; // Create a new template, as innerHTML would be empty for TemplateElements when the fragment is appended directly this._oTemplate = document.createElement("div"); // Make the shadow DOM available in the DOM this._oTemplate.appendChild(oFragment); } // unset any preprocessors (e.g. from an enclosing HTML view) return runWithPreprocessors(function() { if (this.fnScopedRunWithOwner) { this.fnScopedRunWithOwner(function () { DeclarativeSupport.compile(this._oTemplate, this); }.bind(this)); } else { DeclarativeSupport.compile(this._oTemplate, this); } // FIXME declarative support automatically inject the content into this through "this.addContent()" var content = this.getContent(); if (content && content.length === 1) { this._aContent = [content[0]]; return new Promise(function(resolve, reject) { resolve(this._aContent[0]); }.bind(this)); }// else { // TODO: error //} }.bind(this), { settings: this._oContainingView._fnSettingsPreprocessor }); } }); }()); // end of HTML Fragment stuff return Fragment; });