UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

578 lines (534 loc) 20.9 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ /** * This class provides the possibility to declare the "view" part of a composite control * in an XML fragment which will automatically define the rendering accordingly. * * <b>Note:</b> If you use aggregation forwarding with <code>idSuffix</code> as defined * in {@link sap.ui.base.ManagedObject ManagedObject} and refer to IDs defined in the XML fragment * of the XML composite control, then these types of <code>idSuffix</code> have the form * "--ID" where ID is the ID that you have defined in the XML fragment. * */ sap.ui.define([ 'sap/ui/core/Control', 'sap/ui/core/XMLCompositeMetadata', 'sap/ui/model/base/ManagedObjectModel', 'sap/ui/core/Fragment', 'sap/ui/model/resource/ResourceModel', 'sap/base/Log', 'sap/ui/performance/Measurement' ], function( Control, XMLCompositeMetadata, ManagedObjectModel, Fragment, ResourceModel, Log, Measurement ) { "use strict"; // private functions var sXMLComposite = "sap.ui.core.XMLComposite"; /** * XMLComposite is the base class for composite controls that use a XML fragment representation * for their visual parts. From a user perspective such controls appear as any other control, but internally the * rendering part is added as a fragment. * The fragment that is used should appear in the same folder as the control's JS implementation with the file extension * <code>.control.xml</code>. * The fragment's content can access the interface data from the XMLComposite control via bindings. Currently only aggregations and properties * can be used with bindings inside a fragment. * The exposed model that is used for internal bindings in the fragment has the default name <code>$this</code>. The name will always start * with an <code>$</code>. The metadata of the derived control can define the alias with its metadata. A code example can be found below. * * As XMLComposites compose other controls, they are only invalidated and re-rendered if explicitly defined. Additional metadata * for invalidation can be given for properties and aggregation. The default invalidation is <code>"none"</code>. * Setting invalidate to <code>true</code> for properties and aggregations sets the complete XMLComposite * to invalidate and rerender.</code> * * Example: * <pre> * XMLComposite.extend("sap.mylib.MyXMLComposite", { * metadata : { * library: "sap.mylib", * properties : { * text: { //changing this property will not re-render the XMLComposite * type: "string", * defaultValue: "" * }, * title: { //changing this property will re-render the XMLComposite as it defines invalidate: true * type: "string", * defaultValue: "", * invalidate: true * }, * value: { //changing this property will re-render the XMLComposite as it defines invalidate: true * type: "string", * defaultValue: "", * invalidate: true * } * }, * defaultProperty : "text", * aggregations : { * items : { * type: "sap.ui.core.Control", * invalidate: true * }, * header : { * type: "sap.mylib.FancyHeader", * multiple : false * } * }, * defaultAggregation : "items" * events: { * outerEvent : { * parameters : { * opener : "sap.ui.core.Control" * } * } * } * }, * //alias defaults to "this" * alias: "mycontrolroot" //inner bindings will use model name $mycontrolroot * //fragment defaults to {control name}.control.xml in this case sap.mylib.MyXMLComposite.control.xml * fragment: "sap.mylib.MyXMLCompositeOther.control.xml" //the name of the fragment * }); * </pre> * * Internally the XMLComposite instantiates and initializes the given fragment and stores the resulting control in a hidden * aggregation named <code>_content</code>. The fragment should only include one root element. * * Bindings of inner controls to the interface of the XMLComposite can be done with normal binding syntax. * Here properties are used as property bindings and aggregations are used as list bindings. * Currently it is not possible to bind associations in a fragment. * * Example: * <pre> * &lt;core:FragmentDefinition xmlns:m="sap.m" xmlns:core="sap.ui.core"&gt; * &lt;m:Text text="{$this&gt;text}" visible="{= ${$this&gt;text} !== ""}" /&gt; * &lt;/core:FragmentDefinition&gt; * </pre> * <pre> * &lt;core:FragmentDefinition xmlns:m="sap.m" xmlns:core="sap.ui.core"&gt; * &lt;m:VBox items="{path:"$this&gt;texts", filters:{path:"text", operator:"Contains", value1:"Text"}, sorter:{path:"text", descending:true}}"&gt; * &lt;m:Text text="{$this&gt;text}" /&gt; * &lt;/m:VBox&gt; * &lt;/core:FragmentDefinition&gt; * </pre> * <pre> * &lt;core:FragmentDefinition xmlns:m="sap.m" xmlns:core="sap.ui.core"&gt; * &lt;m:Button text="Press Me" press="handlePress"/&gt; * &lt;/core:FragmentDefinition&gt; * </pre> * * All events handled within the fragment will be dispatched to the XMLComposite control. It is recommended to follow this paradigm to allow * reuse of a XMLComposite without any dependency to controller code of the current embedding view. * * <pre> * MyXMLComposite.prototype.handlePress = function() { * this.fireOuterEvent(); // passing on the event to the outer view * } * </pre> * * <b>Note:</b> {@link topic:b11d853a8e784db6b2d210ef57b0f7d7 Requiring modules in XML} will result in side effects that might cause the XMLComposite to not work properly. * We suggest you require the needed modules inside the JavaScript coding of the class extending the XMLComposite. * * @see sap.ui.core.Control * @see sap.ui.core.Fragment * * @class Base Class for XMLComposite controls. * @extends sap.ui.core.Control * * @author SAP SE * @version 1.111.5 * @since 1.56.0 * @alias sap.ui.core.XMLComposite * @see {@link topic:b83a4dcb7d0e46969027345b8d32fd44 XML Composite Controls} * * @abstract * @public * @deprecated As of version 1.88, use {@link topic:c1512f6ce1454ff1913e3857bad56392 Standard Composite Controls} * @experimental Since 1.56.0 */ var XMLComposite = Control.extend("sap.ui.core.XMLComposite", { metadata: { interfaces: ["sap.ui.core.IDScope"], properties: { /** * The width */ width: { type: "sap.ui.core.CSSSize", group: "Dimension", defaultValue: '100%', invalidate: true }, /** * The height */ height: { type: "sap.ui.core.CSSSize", group: "Dimension", defaultValue: null, invalidate: true }, /** * Whether the CSS display should be set to "block". */ displayBlock: { type: "boolean", group: "Appearance", defaultValue: true, invalidate: true } }, aggregations: { /** * Aggregation used to store the default content * @private */ _content: { type: "sap.ui.core.Control", multiple: false, visibility: "hidden", invalidate: true } } }, constructor : function() { this._bIsCreating = true; Control.apply(this, arguments); delete this._bIsCreating; }, renderer: { apiVersion: 2, render: function (oRm, oControl) { Log.debug("Start rendering '" + oControl.sId, sXMLComposite); Measurement.start(oControl.getId() + "---renderControl","Rendering of " + oControl.getMetadata().getName(), ["rendering","control"]); oRm.openStart("div", oControl); oRm.accessibilityState(oControl); // compare ViewRenderer.js - we negate since opposite default if (!oControl.getDisplayBlock() && (oControl.getWidth() !== "100%" || oControl.getHeight() !== "100%")) { oRm.style("display", "inline-block"); } // add inline styles oRm.style("height", oControl.getHeight()); oRm.style("width", oControl.getWidth()); oRm.openEnd(); // render the content var oContent = oControl._renderingContent ? oControl._renderingContent() : oControl._getCompositeAggregation(); if (oContent) { oRm.renderControl(oContent); } oRm.close("div"); Measurement.end(oControl.getId() + "---renderControl"); Log.debug("Stop rendering '" + oControl.sId, sXMLComposite); } } }, XMLCompositeMetadata); /** * Returns an element by its ID in the context of the XMLComposite. * * May only be used by the implementation of a specific XMLComposite, not by an application using a XMLComposite. * * @param {string} sId XMLComposite-local ID of the inner element * @returns {sap.ui.core.Element|undefined} element by its ID or <code>undefined</code> * @protected */ XMLComposite.prototype.byId = function (sId) { return sap.ui.getCore().byId(Fragment.createId(this.getId(), sId)); }; /** * Returns the managed object model of the XMLComposite control * * @returns {sap.ui.model.base.ManagedObjectModel} the managed object model of the XMLComposite control * * @private */ XMLComposite.prototype._getManagedObjectModel = function () { if (!this._oManagedObjectModel) { this._oManagedObjectModel = new ManagedObjectModel(this); } return this._oManagedObjectModel; }; /** * Checks whether invalidation should be suppressed for the given aggregations * Suppressing an aggregation update will only lead to rendering of the changed subtree * * @param {string} sName the name of the aggregation to check * @param {boolean} [bSuppressInvalidate] the requested invalidation or undefined * * @private * */ XMLComposite.prototype.getSuppressInvalidateAggregation = function (sName, bSuppressInvalidate) { var oMetadata = this.getMetadata(), oAggregation = oMetadata.getAggregation(sName) || oMetadata.getAllPrivateAggregations()[sName]; if (!oAggregation) { return true; } bSuppressInvalidate = oMetadata._suppressInvalidate(oAggregation, bSuppressInvalidate); return bSuppressInvalidate; }; /** * @see sap.ui.core.Control#setProperty */ XMLComposite.prototype.setProperty = function (sName, oValue, bSuppressInvalidate) { var oMetadata = this.getMetadata(), oProperty = oMetadata.getManagedProperty(sName); if (!oProperty) { return this; } bSuppressInvalidate = oMetadata._suppressInvalidate(oProperty, bSuppressInvalidate); return Control.prototype.setProperty.apply(this, [sName, oValue, bSuppressInvalidate]); }; /** * @see sap.ui.core.Control#setAggregation */ XMLComposite.prototype.setAggregation = function (sName, oObject, bSuppressInvalidate) { return Control.prototype.setAggregation.apply(this, [sName, oObject, this.getSuppressInvalidateAggregation(sName, bSuppressInvalidate)]); }; /** * @see sap.ui.core.Control#addAggregation */ XMLComposite.prototype.addAggregation = function (sName, oObject, bSuppressInvalidate) { return Control.prototype.addAggregation.apply(this, [sName, oObject, this.getSuppressInvalidateAggregation(sName, bSuppressInvalidate)]); }; /** * @see sap.ui.core.Control#unbindAggregation */ XMLComposite.prototype.insertAggregation = function (sName, oObject, iIndex, bSuppressInvalidate) { return Control.prototype.insertAggregation.apply(this, [sName, oObject, iIndex, this.getSuppressInvalidateAggregation(sName, bSuppressInvalidate)]); }; /** * sap.ui.core.Control#removeAggregation */ XMLComposite.prototype.removeAggregation = function (sName, oObject, bSuppressInvalidate) { return Control.prototype.removeAggregation.apply(this, [sName, oObject, this.getSuppressInvalidateAggregation(sName, bSuppressInvalidate)]); }; /** * @see sap.ui.core.Control#removeAllAggregation */ XMLComposite.prototype.removeAllAggregation = function (sName, bSuppressInvalidate) { return Control.prototype.removeAllAggregation.apply(this, [sName, this.getSuppressInvalidateAggregation(sName, bSuppressInvalidate)]); }; /** * @see sap.ui.core.Control#destroyAggregation */ XMLComposite.prototype.destroyAggregation = function (sName, bSuppressInvalidate) { return Control.prototype.destroyAggregation.apply(this, [sName, this.getSuppressInvalidateAggregation(sName, bSuppressInvalidate)]); }; /** * @see sap.ui.core.Control#updateAggregation */ XMLComposite.prototype.updateAggregation = function (sName, bSuppressInvalidate) { var oAggregation = this.getMetadata().getAggregation(sName); if (oAggregation && oAggregation.type === "TemplateMetadataContext") { this.invalidate(); return; } Control.prototype.updateAggregation.apply(this, arguments); }; /** * @see sap.ui.core.Control#setVisible */ XMLComposite.prototype.setVisible = function (bVisible) { this.setProperty("visible", bVisible); if (this.getParent()) { // TODO: is this correct ? this.getParent().invalidate(); } return this; }; /** * Destroys the internal composite aggregation * * @returns {this} Returns <code>this</code> to allow method chaining * * @private */ XMLComposite.prototype._destroyCompositeAggregation = function () { var oContent = this._getCompositeAggregation(); if (oContent) { oContent.destroy("KeepDom"); } return this; }; /** * Whenever bindings are updated the corresponding aggregations need to be destroyed, * otherwise the managed object tree is not updating the proxy object in the inner managed object tree. */ XMLComposite.prototype.updateBindings = function () { if (this._bIsCreating) { return; } var oResult = Control.prototype.updateBindings.apply(this, arguments); for (var n in this.mBindingInfos) { var oAggregation = this.getMetadata().getAggregation(n); if (oAggregation && oAggregation.multiple && !oAggregation._doesNotRequireFactory && this.isBound(n) && !this.getBinding(n)) { this[oAggregation._sDestructor](); } } return oResult; }; /** * Returns the composite aggregation */ XMLComposite.prototype._getCompositeAggregation = function () { var sCompositeName = this.getMetadata().getCompositeAggregationName(); return this.getAggregation(sCompositeName); }; /** * Sets the internal composite aggregation * * @returns {this} Returns <code>this</code> to allow method chaining * * @private */ XMLComposite.prototype._setCompositeAggregation = function (oNewContent) { var sCompositeName = this.getMetadata().getCompositeAggregationName(); this._destroyCompositeAggregation(); if (!this._oManagedObjectModel) { this._getManagedObjectModel(); } if (Array.isArray(oNewContent)) { this.setAggregation(sCompositeName, null); return; } if (oNewContent) { //accessibility if (!oNewContent.enhanceAccessibilityState) { oNewContent.enhanceAccessibilityState = function(oElement, mAriaProps) { this.enhanceAccessibilityState(oElement, mAriaProps); }.bind(this); } oNewContent.bindObject("$" + this.alias + ">/");//first define the context oNewContent.setModel(this._oManagedObjectModel, "$" + this.alias);//then set the model if (this.bUsesI18n) { var oResourceModel = this._getResourceModel(); if (oResourceModel) { oNewContent.setModel(oResourceModel, "$" + this.alias + ".i18n"); } } } this.setAggregation(sCompositeName, oNewContent); }; /** * Already loaded resource models keyed by library. The key in the map is always libraryName + ".messagebundle" * @private * @see getLibraryResourceModel */ XMLComposite.mResourceModels = {}; XMLComposite.getLibraryResourceModel = function (sLibraryName) { var oLibraryResourceModel = XMLComposite.mResourceModels[sLibraryName]; if (!oLibraryResourceModel) { oLibraryResourceModel = new ResourceModel({ bundleName: sLibraryName + ".messagebundle", async: true }); XMLComposite.mResourceModels[sLibraryName] = oLibraryResourceModel; } return oLibraryResourceModel; }; /** * if a messageBundle is specified from outside we rely on this, otherwise we take the library bundle * * @returns: undefined if no model can be found * * @private */ XMLComposite.prototype._getResourceModel = function () { if (this.resourceModel) { return this.resourceModel; } if (this.messageBundle) { // was set a bundle name from outside - in this case the control will get its own resourceModel this.resourceModel = new ResourceModel({ bundleName: this.messageBundle, async: true }); return this.resourceModel; } else { // we rely on the library bundle this.sLibraryName = this.sLibraryName || this.getMetadata().getLibraryName(); if (this.sLibraryName) { return XMLComposite.getLibraryResourceModel(this.sLibraryName); } } }; /** * Returns the resource bundle of the resource model * * Sample: this.getResourceBundle().then(function(oBundle) {oBundle.getText(<messagebundle_key>)}) * * @returns {module:sap/base/i18n/ResourceBundle|Promise} loaded resource bundle or ECMA Script 6 Promise in asynchronous case * * @public */ XMLComposite.prototype.getResourceBundle = function () { var oResourceModel = this._getResourceModel(); return oResourceModel ? oResourceModel.getResourceBundle() : null; }; XMLComposite.prototype.destroy = function () { Control.prototype.destroy.apply(this, arguments); if (this.resourceModel) { this.resourceModel.destroy(); } if (this._oManagedObjectModel) { this._oManagedObjectModel.destroy(); } }; /** * Initializes composite support with the given settings * @param {map} mSettings the map of settings * * @private */ XMLComposite.prototype._initCompositeSupport = function (mSettings) { var oMetadata = this.getMetadata(), oFragmentContent = oMetadata._fragment, sAggregationName = oMetadata.getCompositeAggregationName(); this._destroyCompositeAggregation(); //identify the _fragmentContent the new template wins if (mSettings && sAggregationName && mSettings[sAggregationName]) {//or from the settings var oNode = mSettings[sAggregationName]; if (oNode.localName === "FragmentDefinition") {//should be always the case oFragmentContent = oNode; delete mSettings[sAggregationName]; } } var sFragment = oFragmentContent ? (new XMLSerializer()).serializeToString(oFragmentContent) : undefined; this.bUsesI18n = sFragment ? (sFragment.indexOf("$" + this.alias + ".i18n") != -1) : true; this._setCompositeAggregation(sap.ui.xmlfragment({ // legacy-relevant: can lead to follow-up sync XHRs for controls sId: this.getId(), fragmentContent: oFragmentContent, oController: this })); this._bIsInitialized = true; }; /** * This method is a hook for the RenderManager that gets called * during the rendering of child Controls. It allows to add, * remove and update existing accessibility attributes (ARIA) of * those controls. * * @param {sap.ui.core.Control} oElement - The Control that gets rendered by the RenderManager * @param {object} mAriaProps - The mapping of "aria-" prefixed attributes * @protected */ XMLComposite.prototype.enhanceAccessibilityState = function(oElement, mAriaProps) { var oParent = this.getParent(); if (oParent && oParent.enhanceAccessibilityState) { // use XMLComposite as control, but aria properties of rendered inner controls. oParent.enhanceAccessibilityState(this, mAriaProps); } }; /** * Return the focus DOM Reference for accessibility */ XMLComposite.prototype.getFocusDomRef = function() { var oContent = this._renderingContent ? this._renderingContent() : this._getCompositeAggregation(); return oContent.getFocusDomRef(); }; /** * Return the focus DOM Reference for accessibility */ XMLComposite.prototype.getFocusInfo = function() { var oContent = this._renderingContent ? this._renderingContent() : this._getCompositeAggregation(); return oContent.getFocusInfo(); }; /** * Return the id the label control should point to, ideally the rendering content */ XMLComposite.prototype.getIdForLabel = function() { var oContent = this._renderingContent ? this._renderingContent() : this._getCompositeAggregation(); return oContent.getIdForLabel(); }; return XMLComposite; });