@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
529 lines (473 loc) • 18.6 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.
*/
// Provides control sap.ui.core.ComponentContainer.
sap.ui.define([
'sap/ui/base/ManagedObject',
'./Control',
'./Component',
'./library',
"./ComponentContainerRenderer",
"sap/base/Log",
"sap/ui/core/Configuration"
],
function(
ManagedObject,
Control,
Component,
library,
ComponentContainerRenderer,
Log,
Configuration
) {
"use strict";
var ComponentLifecycle = library.ComponentLifecycle;
/**
* Constructor for a new ComponentContainer.
*
* @param {string} [sId] id for the new control, generated automatically if no id is given
* @param {object} [mSettings] initial settings for the new control
*
* @class Container that embeds a <code>sap/ui/core/UIComponent</code> in a control tree.
*
* <b>Concerning asynchronous component loading:</b>
*
* To activate a fully asynchronous loading behavior of components and their dependencies,
* the property <code>async</code> needs to be set to <code>true</code> and
* the <code>manifest</code> property needs to be set to a 'truthy' value, e.g. <code>true</code> or a URL to the manifest location.
* If both options are correctly set, the component factory will load and evaluate the component manifest first.
* In this way, the additional dependencies of the Component are already known before the Component preload/controller is loaded.
* Both the component preload/controller and the additional dependencies can thus be loaded asynchronously and in parallel.
*
* Sample usage of the ComponentContainer:
*
* <pre>
* <!-- inside XML view -->
* ...
* <core:ComponentContainer
* usage="someComponent"
* manifest="true"
* async="true"
* />
* </pre>
*
* See also {@link module:sap/ui/core/ComponentSupport}.
*
* @extends sap.ui.core.Control
* @version 1.111.5
*
* @public
* @alias sap.ui.core.ComponentContainer
*/
var ComponentContainer = Control.extend("sap.ui.core.ComponentContainer", /** @lends sap.ui.core.ComponentContainer.prototype */ {
metadata : {
interfaces: [
"sap.ui.core.IPlaceholderSupport"
],
library : "sap.ui.core",
properties : {
/**
* Component name, the package where the component is contained. This property can only be applied initially.
*/
name : {type : "string", defaultValue : null},
/**
* The URL of the component. This property can only be applied initially.
*/
url : {type : "sap.ui.core.URI", defaultValue : null},
/**
* Flag whether the component should be created sync (default) or async. The default
* will be async when initially the property <code>manifest</code> is set to a truthy
* value and for the property <code>async</code> no value has been specified.
* This property can only be applied initially.
*/
async : {type : "boolean", defaultValue : false},
/**
* Enable/disable validation handling by MessageManager for this component.
* The resulting Messages will be propagated to the controls.
* This property can only be applied initially.
*/
handleValidation : {type : "boolean", defaultValue : false},
/**
* The settings object passed to the component when created. This property can only be applied initially.
*/
settings : {type : "object", defaultValue : null},
/**
* Defines whether binding information is propagated to the component.
*/
propagateModel : {type : "boolean", defaultValue : false},
/**
* Container width in CSS size
*/
width : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null},
/**
* Container height in CSS size
*/
height : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null},
/**
* Lifecycle behavior for the Component associated by the <code>ComponentContainer</code>.
* The default behavior is <code>Legacy</code>. This means that the <code>ComponentContainer</code>
* takes care that the Component is destroyed when the <code>ComponentContainer</code> is destroyed,
* but it is <b>not</b> destroyed when a new Component is associated.
* If you use the <code>usage</code> property to create the Component,
* the default behavior is <code>Container</code>. This means that
* the Component is destroyed when the <code>ComponentContainer</code> is destroyed or a new
* Component is associated.
* This property must only be applied before a component instance is created.
*/
lifecycle : {type : "sap.ui.core.ComponentLifecycle", defaultValue : ComponentLifecycle.Legacy},
/**
* Flag, whether to auto-prefix the ID of the nested Component or not. If
* this property is set to true the ID of the Component will be prefixed
* with the ID of the ComponentContainer followed by a single dash.
* This property can only be applied initially.
*/
autoPrefixId : {type : "boolean", defaultValue: false},
/**
* The name of a component usage as configured in the app descriptor of the component
* owning this <code>ComponentContainer</code>.
*
* The configuration from the named usage will be used to create a component instance for this
* <code>ComponentContainer</code>. If there's no owning component or if its app descriptor
* does not contain a usage with the given name, an error will be logged.
*
* See {@link topic:346599f0890d4dfaaa11c6b4ffa96312 Using and Nesting Components} for more
* information about component usages.
*
* This property can only be applied initially.
*/
usage : {type : "string", defaultValue : null},
/**
* Controls when and from where to load the manifest for the Component.
* When set to any truthy value, the manifest will be loaded asynchronously by default
* and evaluated before the Component controller, if it is set to a falsy value
* other than <code>undefined</code>, the manifest will be loaded after the controller.
* A non-empty string value will be interpreted as the URL location from where to load the manifest.
* A non-null object value will be interpreted as manifest content.
* This property can only be applied initially.
*/
manifest: {type : "any" /* type: "string|boolean|object" */, defaultValue : null}
},
associations : {
/**
* The component displayed in this ComponentContainer.
*/
component : {type : "sap.ui.core.UIComponent", multiple : false}
},
events : {
/**
* Fired when the component instance has been created by the
* ComponentContainer.
* @since 1.50
*/
componentCreated : {
parameters : {
/**
* Reference to the created component instance
*/
component : { type: "sap.ui.core.UIComponent" }
}
},
/**
* Fired when the creation of the component instance has failed.
*
* By default, the <code>ComponentContainer</code> also logs the error that occurred.
* Since 1.83, this default behavior can be prevented by calling <code>preventDefault()</code>
* on the event object.
*
* @since 1.60
*/
componentFailed : {
allowPreventDefault: true,
parameters : {
/**
* The reason object as returned by the component promise
*/
reason : { type: "object" }
}
}
},
designtime: "sap/ui/core/designtime/ComponentContainer.designtime"
},
renderer: ComponentContainerRenderer
});
/*
* Helper function to set the new Component of the container.
*/
function setContainerComponent(oComponentContainer, vComponent, bSuppressInvalidate, bDestroyOldComponent) {
// find the reference to the current component and to the old component
var oComponent = typeof vComponent === "string" ? Component.get(vComponent) : vComponent;
var oOldComponent = oComponentContainer.getComponentInstance();
// if there is no difference between the old and the new component just skip this setter
if (oOldComponent !== oComponent) {
// unlink the old component from the container
if (oOldComponent) {
oOldComponent.setContainer(undefined);
if (bDestroyOldComponent) {
oOldComponent.destroy();
} else {
// cleanup the propagated properties in case of not destroying the component
oComponentContainer._propagateProperties(true, oOldComponent, ManagedObject._oEmptyPropagatedProperties, true);
}
}
// set the new component
oComponentContainer.setAssociation("component", oComponent, bSuppressInvalidate);
// cross link the new component and propagate the properties (models)
oComponent = oComponentContainer.getComponentInstance();
if (oComponent) {
oComponent.setContainer(oComponentContainer);
oComponentContainer.propagateProperties(true); //propagate all
}
}
}
/**
* Returns the real component instance which is associated with the container.
* @return {sap.ui.core.UIComponent} the component instance
*/
ComponentContainer.prototype.getComponentInstance = function () {
var sComponentId = this.getComponent();
return sComponentId && Component.get(sComponentId);
};
// Delegate registered by the ComponentContainer#showPlaceholder function
var oPlaceholderDelegate = {
/**
* @this {sap.ui.core.ComponentContainer}
* @private
*/
"onAfterRendering": function() {
// check whether the placeholder is still active. If yes, show the placeholder again
if (this._placeholder) {
this._placeholder.show(this);
}
}
};
/**
* Shows the provided placeholder on the component container.
*
* @param {object} mSettings Object containing the placeholder object
* @param {sap.ui.core.Placeholder} mSettings.placeholder The placeholder instance
* @return {Promise} Promise that resolves with the placeholder
*
* @private
* @ui5-restricted SAPUI5 Distribution Layer Libraries
* @since 1.91
*/
ComponentContainer.prototype.showPlaceholder = function(mSettings) {
var pLoaded;
if (!Configuration.getPlaceholder()) {
return;
}
if (this._placeholder) {
this.hidePlaceholder();
}
if (mSettings.placeholder) {
this._placeholder = mSettings.placeholder;
pLoaded = this._placeholder._load();
} else {
pLoaded = Promise.resolve();
}
if (this.getDomRef() && this._placeholder) {
this._placeholder.show(this);
}
// Add an event delegate to reinsert the placeholder after it's removed after a rerendering
this.addEventDelegate(oPlaceholderDelegate, this);
return pLoaded;
};
/**
* Hides the placeholder that is shown on the component container.
*
* @private
* @ui5-restricted SAP internal apps
* @since 1.91
*/
ComponentContainer.prototype.hidePlaceholder = function() {
if (this._placeholder) {
this._placeholder.hide();
// remove the delegate because the placeholder is hidden
this.removeEventDelegate(oPlaceholderDelegate);
this._placeholder = undefined;
}
};
/**
* Sets the component of the container. Depending on the ComponentContainer's
* lifecycle this might destroy the old associated Component.
*
* Once the component is associated with the container the cross connection
* to the component will be set and the models will be propagated if defined.
* If the <code>usage</code> property is set the ComponentLifecycle is processed like a "Container" lifecycle.
*
* @param {string|sap.ui.core.UIComponent} vComponent ID of an element which becomes the new target of this component association. Alternatively, an element instance may be given.
* @return {this} the reference to <code>this</code> in order to allow method chaining
* @public
*/
ComponentContainer.prototype.setComponent = function(vComponent, bSuppressInvalidate) {
setContainerComponent(this, vComponent, bSuppressInvalidate,
this.getLifecycle() === ComponentLifecycle.Container
|| (typeof this.getUsage() === "string" && this.getUsage() && this.getLifecycle() === ComponentLifecycle.Legacy)
);
return this;
};
/*
* overrule and adopt initial values
*/
ComponentContainer.prototype.applySettings = function(mSettings, oScope) {
if (mSettings) {
// The "manifest" property has type "any" to be able to handle string|boolean|object.
// When using the ComponentContainer in a declarative way (e.g. XMLView), boolean values
// are passed as string. Therefore this type conversion needs to be done manually.
// As this use-case is only relevant initially the handling is done in "applySettings"
// instead of overriding "setManifest".
if (mSettings.manifest === "true" || mSettings.manifest === "false") {
mSettings.manifest = mSettings.manifest === "true";
}
// a truthy value for the manifest property will set the property
// async to true if not provided initially
if (mSettings.manifest && mSettings.async === undefined) {
mSettings.async = true;
}
}
Control.prototype.applySettings.apply(this, arguments);
};
/*
* Helper to create the settings object for the Component Factory or the
* createComponent function.
*/
function createComponentConfig(oComponentContainer) {
var sName = oComponentContainer.getName();
var vManifest = oComponentContainer.getManifest();
var sUrl = oComponentContainer.getUrl();
var mSettings = oComponentContainer.getSettings();
var mConfig = {
name: sName ? sName : undefined,
manifest: vManifest !== null ? vManifest : false,
async: oComponentContainer.getAsync(),
url: sUrl ? sUrl : undefined,
handleValidation: oComponentContainer.getHandleValidation(),
settings: mSettings !== null ? mSettings : undefined
};
return mConfig;
}
/**
* Private helper to create the component instance based on the
* configuration of the Component Container
* @return {Promise|sap.ui.core.Component} a Promise for async and for sync scenarios a Component instance
* @private
*/
ComponentContainer.prototype._createComponent = function() {
// determine the owner component
var oOwnerComponent = Component.getOwnerComponentFor(this),
sUsageId = this.getUsage(),
mConfig = createComponentConfig(this);
// First, enhance the config object with "usage" definition from manifest
if (sUsageId) {
if (oOwnerComponent) {
mConfig = oOwnerComponent._enhanceWithUsageConfig(sUsageId, mConfig);
} else {
Log.error("ComponentContainer \"" + this.getId() + "\" does have a \"usage\", but no owner component!");
}
}
// Then, prefix component ID with the container ID, as the ID might come from
// the usage configuration in the manifest
if (this.getAutoPrefixId()) {
if (mConfig.id) {
mConfig.id = this.getId() + "-" + mConfig.id;
}
if (mConfig.settings && mConfig.settings.id) {
mConfig.settings.id = this.getId() + "-" + mConfig.settings.id;
}
}
// Finally, create the component instance
return Component._createComponent(mConfig, oOwnerComponent);
};
/*
* delegate the onBeforeRendering to the component instance
*/
ComponentContainer.prototype.onBeforeRendering = function() {
// check if we have already a valid component instance
// in this case we skip the component creation via props
// ==> not in applySettings to make sure that components are lazy instantiated,
// e.g. in case of invisible containers the component will not be created
// immediately in the constructor.
var oComponent = this.getComponentInstance(),
sUsage = this.getUsage(),
sName = this.getName(),
sManifest = this.getManifest();
if (!this._oComponentPromise && !oComponent && (sUsage || sName || sManifest)) {
// create the component instance with the local configuration
oComponent = this._createComponent();
// check whether it is needed to delay to set the component or not
if (oComponent instanceof Promise) {
this._oComponentPromise = oComponent;
oComponent.then(function(oComponent) {
delete this._oComponentPromise;
// set the component and invalidate to ensure a re-rendering!
this.setComponent(oComponent);
// notify listeners that a new component instance has been created
this.fireComponentCreated({
component: oComponent
});
}.bind(this), function(oReason) {
delete this._oComponentPromise;
// listeners can prevent the default log entry
if ( this.fireComponentFailed({ reason: oReason }) ) {
Log.error("Failed to load component for container " + this.getId(), oReason);
}
}.bind(this));
} else if (oComponent) {
this.setComponent(oComponent, true);
// notify listeners that a new component instance has been created
this.fireComponentCreated({
component: oComponent
});
} else {
this.fireComponentFailed({
reason: new Error("The component could not be created.")
});
}
}
// delegate the onBeforeRendering to the component instance
if (oComponent && oComponent.onBeforeRendering) {
oComponent.onBeforeRendering();
}
};
/*
* delegate the onAfterRendering to the component instance
*/
ComponentContainer.prototype.onAfterRendering = function() {
var oComponent = this.getComponentInstance();
if (oComponent && oComponent.onAfterRendering) {
oComponent.onAfterRendering();
}
};
/*
* once the container is destroyed we remove the reference to the container
* in the component and destroy the component unless its lifecycle is managed
* by the application.
*/
ComponentContainer.prototype.exit = function() {
setContainerComponent(this, undefined, true,
this.getLifecycle() !== ComponentLifecycle.Application);
};
/*
* overridden to support property propagation to the associated component
*/
ComponentContainer.prototype.propagateProperties = function (vName) {
var oComponent = this.getComponentInstance();
if (oComponent && this.getPropagateModel()) {
this._propagateProperties(vName, oComponent);
}
Control.prototype.propagateProperties.apply(this, arguments);
};
/*
* overridden to support contextual settings propagation to the associated component
* no need to call the parent prototype method as there are no aggregations to propagate to
*/
ComponentContainer.prototype._propagateContextualSettings = function () {
var oComponent = this.getComponentInstance();
if (oComponent) {
oComponent._applyContextualSettings(this._getContextualSettings());
}
};
return ComponentContainer;
});