@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
578 lines (534 loc) • 20.9 kB
JavaScript
/*!
* 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>
* <core:FragmentDefinition xmlns:m="sap.m" xmlns:core="sap.ui.core">
* <m:Text text="{$this>text}" visible="{= ${$this>text} !== ""}" />
* </core:FragmentDefinition>
* </pre>
* <pre>
* <core:FragmentDefinition xmlns:m="sap.m" xmlns:core="sap.ui.core">
* <m:VBox items="{path:"$this>texts", filters:{path:"text", operator:"Contains", value1:"Text"}, sorter:{path:"text", descending:true}}">
* <m:Text text="{$this>text}" />
* </m:VBox>
* </core:FragmentDefinition>
* </pre>
* <pre>
* <core:FragmentDefinition xmlns:m="sap.m" xmlns:core="sap.ui.core">
* <m:Button text="Press Me" press="handlePress"/>
* </core:FragmentDefinition>
* </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;
});