@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
458 lines (401 loc) • 16.9 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
"sap/base/Log",
"sap/ui/core/ComponentContainer",
"sap/ui/core/library"
], function(Log, ComponentContainer, coreLib) {
"use strict";
// shortcut for sap.ui.core.ComponentLifecycle
var ComponentLifecycle = coreLib.ComponentLifecycle;
/**
* Provide methods for sap.ui.core.routing.Target in async mode
* @private
* @experimental
* @since 1.33
*/
return {
/**
* Creates a view and puts it in an aggregation of a control that has been defined in the {@link #constructor}.
*
* This method can be used to display a target without changing the browser hash. If the browser hash should be changed,
* the {@link sap.ui.core.routing.Router#navTo} method should be used instead
*
* @param {*} [vData] An object that will be passed to the display event in the data property. If the target has parents, the data will also be passed to them.
* @return {Promise} Resolves with {name: *, view: *, control: *} if the target can be successfully displayed otherwise it rejects with error information
* @private
*/
display: function (vData) {
// Create an immediately resolving promise for parentless Target
var oSequencePromise = Promise.resolve();
return this._display(vData, oSequencePromise);
},
/**
* Creates a view and puts it in an aggregation of a control that has been defined in the {@link #constructor}.
*
* @param {*} [vData] An object that will be passed to the display event in the data property. If the target has parents, the data will also be passed to them.
* @param {Promise} oSequencePromise Promise chain for resolution in the correct order
* @param {object} [oTargetCreateInfo] Additional information for the component creation. Currently the object only contains the prefix for the routerHashChanger
* @returns {Promise} Resolves with {name: *, view: *, control: *} if the target can be successfully displayed otherwise it rejects with error information
* @private
*/
_display: function (vData, oSequencePromise, oTargetCreateInfo) {
if (this._oParent) {
// replace the sync
oSequencePromise = this._oParent._display(vData, oSequencePromise, oTargetCreateInfo);
}
return this._place(vData, oSequencePromise, oTargetCreateInfo);
},
/**
* Suspends the object which is loaded by the target.
*
* Currently this function stops the router of the component when
* the object which is loaded by this target is an instance of
* UIComponent. When the target is still being loaded or not loaded
* yet, this function has no effect.
*
* @return {sap.ui.core.routing.Target} The 'this' to chain the call
* @private
*/
suspend: function() {
if (this._oParent) {
this._oParent.suspend();
}
if (this._isLoaded()) {
var oObject = this._get(),
oRouter;
if (oObject.isA("sap.ui.core.UIComponent") && (oRouter = oObject.getRouter()) && oObject.hasNativeRouter()) {
oRouter.stop();
}
} else {
Log.warning("The target with name '" + this._oOptions._name + "' can't be suspended because it's being loaded or not loaded yet");
}
return this;
},
/**
* Checks whether the object which this Target loads is already loaded
*
* @return {boolean} Whether the object which this Target loads is already loaded
* @private
*/
_isLoaded: function() {
return this._bIsLoaded;
},
/**
* Get the target instance from the TargetCache.
*
* The difference between this function and the "_load" function is that this function returns the target
* instance directly if it's already loaded and returns a Promise during the loading of the target instance
* while the "_load" function always returns a promise no matter whether the target instance is loaded or not.
*
* @param {object} [oTargetCreateInfo] Additional information for the component creation. Currently the object
* only contains the prefix for the routerHashChanger
* @returns {sap.ui.core.mvc.View|sap.ui.core.UIComponent|Promise} The target instance when it's already loaded
* or a promise which resolves with the target instance during the loading of the target instance
* @private
*/
_get: function(oTargetCreateInfo) {
var sName = this._getEffectiveObjectName(this._oOptions.name),
oOptions = this._oOptions,
oCreateOptions;
switch (oOptions.type) {
case "View":
oCreateOptions = {
name: sName,
type: oOptions.viewType,
id: oOptions.id,
async: true
};
break;
case "Component":
oCreateOptions = { id: oOptions.id };
if (oOptions.usage) {
oCreateOptions.usage = oOptions.usage;
} else {
oCreateOptions.name = sName;
}
oCreateOptions = Object.assign({}, oOptions.options || {}, oCreateOptions);
break;
default:
throw new Error("The given type " + oOptions.type + " isn't support by sap.ui.core.routing.Target");
}
return this._oCache._get(oCreateOptions, oOptions.type,
// Hook in the route for deprecated global view id, it has to be supported to stay compatible
this._bUseRawViewId, oTargetCreateInfo);
},
/**
* Loads the object from TargetCache.
*
* @param {object} [oTargetCreateInfo] Additional information for the component creation. Currently the object
* only contains the prefix for the routerHashChanger
* @return {Promise} A promise which resolves with the loaded object of this Target
* @private
*/
_load: function(oTargetCreateInfo) {
var oObject = this._get(oTargetCreateInfo),
pLoaded;
if (!(oObject instanceof Promise)) {
if (oObject.isA("sap.ui.core.mvc.View")) {
pLoaded = oObject.loaded();
} else {
pLoaded = Promise.resolve(oObject);
}
} else {
pLoaded = oObject;
}
return pLoaded.then(function(oObject) {
this._bIsLoaded = true;
return oObject;
}.bind(this));
},
/**
* Here the magic happens - recursion + placement + view creation needs to be refactored
*
* @param {object} [vData] an object that will be passed to the display event in the data property. If the
* target has parents, the data will also be passed to them.
* @param {Promise} oSequencePromise Promise chain for resolution in the correct order
* @param {object} oTargetCreateInfo Additional information for the component creation. Currently the object only contains the prefix for the routerHashChanger
* @return {Promise} resolves with {name: *, view: *, control: *} if the target can be successfully displayed otherwise it rejects with an error message
* @private
*/
_place: function (vData, oSequencePromise, oTargetCreateInfo) {
if (vData instanceof Promise) {
oTargetCreateInfo = oSequencePromise;
oSequencePromise = vData;
vData = undefined;
}
var oOptions = this._oOptions,
that = this,
oObject, sErrorMessage, pLoaded,
bInstantResolve = true,
fnResolve,
pNestedRouteMatched;
if ((oOptions.name || oOptions.usage) && oOptions.type) {
pNestedRouteMatched = new Promise(function(resolve) {
fnResolve = resolve;
});
pLoaded = this._load(oTargetCreateInfo).then(function (oObject) {
if (oObject.isA("sap.ui.core.UIComponent")) {
var oRouter = oObject.getRouter();
if (oRouter && oObject.hasNativeRouter()) {
var sHash = oRouter.getHashChanger().getHash();
var oRoute = oRouter.getRouteByHash(sHash);
if (!oRouter._oConfig.async){
throw new Error("The router of component '" + oObject.getId() + "' which is loaded via the target '" + that._oOptions._name + "' is defined as synchronous which is not supported using as a nested component.");
}
if (oRouter._oOwner && oTargetCreateInfo) {
// update the flag once the component is displayed again after it's already loaded
oRouter._oOwner._bRoutingPropagateTitle = oTargetCreateInfo.propagateTitle;
}
// TODO: offer getter for target info
//
// The router is normally initialized in the UIComponent.prototype.init function and the
// init function should be already called before it reaches this place which means that the
// router is initialized in most of the cases. If a router is already initialized, we still
// need to check whether the route match process is finished. If it's not finished, we are
// sure that there will be a "routeMatched" event fired and we can wait for it.
if ((!oRouter.isInitialized() || oRouter._bMatchingProcessStarted) && oRoute && oRoute._oConfig.target) {
bInstantResolve = false;
oRouter.attachRouteMatched(fnResolve);
}
if (oRouter.isStopped()) {
// initialize the router in nested component
// if it has been previously stopped
oRouter.initialize();
}
}
}
if (bInstantResolve) {
fnResolve();
}
return oObject;
});
// when target information is given
oSequencePromise = oSequencePromise
.then(function(oParentInfo) {
return pLoaded.then(function(oObject) {
return {
object: oObject,
parentInfo: oParentInfo || {}
};
});
})
.then(function(oViewInfo) {
// loaded and do placement
var vValid = that._isValid(oViewInfo.parentInfo),
oView, oRootControl;
oObject = oViewInfo.object;
if (oObject.isA("sap.ui.core.UIComponent")) {
oRootControl = oObject.getRootControl();
if (oRootControl && oRootControl.isA("sap.ui.core.mvc.View")) {
oView = oRootControl;
}
} else {
oView = oObject;
}
that._bindTitleInTitleProvider(oView);
that._addTitleProviderAsDependent(oView);
// validate config and log errors if necessary
if (vValid !== true) {
sErrorMessage = vValid;
return that._refuseInvalidTarget(oOptions._name, sErrorMessage);
}
var oViewContainingTheControl = oViewInfo.parentInfo.view,
oControl = oViewInfo.parentInfo.control,
pContainerControl = Promise.resolve(oControl);
// if the parent target loads a component, the oViewContainingTheControl is an instance of
// ComponentContainer. The root control of the component should be retrieved and set as
// oViewContainingTheControl
if (oViewContainingTheControl && oViewContainingTheControl.isA("sap.ui.core.ComponentContainer")) {
oViewContainingTheControl = oViewContainingTheControl.getComponentInstance().getRootControl();
}
//no parent view - see if there is a targetParent in the config
if (!oViewContainingTheControl && oOptions.rootView) {
oViewContainingTheControl = sap.ui.getCore().byId(oOptions.rootView);
if (!oViewContainingTheControl) {
sErrorMessage = "Did not find the root view with the id " + oOptions.rootView;
return that._refuseInvalidTarget(oOptions._name, sErrorMessage);
}
}
// Find the control in the parent
if (oOptions.controlId) {
// The root control of a component may be any kind of control
// A check of sap.ui.core.View is needed before calling the loaded method to wait
// for the loading of the view
if (oViewContainingTheControl && oViewContainingTheControl.isA("sap.ui.core.mvc.View")) {
// controlId was specified - ask the parents view for it
// wait for the parent view to be loaded in case it's loaded async
pContainerControl = oViewContainingTheControl.loaded().then(function(oContainerView) {
return oContainerView.byId(oOptions.controlId);
});
}
pContainerControl = pContainerControl.then(function(oContainerControl) {
if (!oContainerControl) {
//Test if control exists in core (without prefix) since it was not found in the parent or root view
oContainerControl = sap.ui.getCore().byId(oOptions.controlId);
}
if (!oContainerControl) {
sErrorMessage = "Control with ID " + oOptions.controlId + " could not be found";
return that._refuseInvalidTarget(oOptions._name, sErrorMessage);
} else {
return oContainerControl;
}
});
}
return pContainerControl;
})
.then(function(oContainerControl) {
var oComponent,
sComponentContainerId,
fnOriginalDestroy;
if (oObject.isA("sap.ui.core.UIComponent")) {
oComponent = oObject;
sComponentContainerId = oComponent.getId() + "-container";
oObject = sap.ui.getCore().byId(sComponentContainerId);
if (!oObject) {
// defaults mixed in with configured settings
var oContainerOptions = Object.assign({
component: oComponent,
height: "100%",
width: "100%",
lifecycle: ComponentLifecycle.Application
}, oOptions.containerOptions);
oObject = new ComponentContainer(sComponentContainerId, oContainerOptions);
fnOriginalDestroy = oComponent.destroy;
oComponent.destroy = function () {
if (fnOriginalDestroy) {
fnOriginalDestroy.apply(this);
}
// destroy the component container when the component is destroyed
oObject.destroy();
};
}
}
// adapt the container before placing the view into it to make the rendering occur together with the next
// aggregation modification.
that._beforePlacingViewIntoContainer({
container: oContainerControl,
view: oObject,
data: vData
});
var oAggregationInfo = oContainerControl.getMetadata().getJSONKeys()[oOptions.controlAggregation];
if (!oAggregationInfo) {
sErrorMessage = "Control " + oOptions.controlId + " does not have an aggregation called " + oOptions.controlAggregation;
return that._refuseInvalidTarget(oOptions._name, sErrorMessage);
}
if (oOptions.clearControlAggregation === true) {
oContainerControl[oAggregationInfo._sRemoveAllMutator]();
}
Log.info("Did place the " + oOptions.type.toLowerCase() + " target '" + (oOptions.name ? that._getEffectiveObjectName(oOptions.name) : oOptions.usage) + "' with the id '" + oObject.getId() + "' into the aggregation '" + oOptions.controlAggregation + "' of a control with the id '" + oContainerControl.getId() + "'", that);
oContainerControl[oAggregationInfo._sMutator](oObject);
return {
name: oOptions._name,
view: oObject,
control: oContainerControl
};
});
} else {
oSequencePromise = oSequencePromise.then(function() {
return {
name: oOptions._name
};
});
}
return Promise.all([oSequencePromise, pNestedRouteMatched]).then(function(aObjects) {
var oContainerControl = aObjects[0].control;
var oObject = aObjects[0].view;
if (oContainerControl && oObject) {
that.fireDisplay({
view : oObject.isA("sap.ui.core.mvc.View") ? oObject : undefined,
object: oObject,
control : oContainerControl,
config : that._oOptions,
data: vData,
routeRelevant: that._routeRelevant
});
}
return aObjects[0];
});
},
/**
* Validates the target options, will also be called from the route but route will not log errors
*
* @param {object} oParentInfo The parent info {name: *, view: *, control: *}
* @returns {boolean|string} returns true if it's valid otherwise the error message
* @private
*/
_isValid : function (oParentInfo) {
var oOptions = this._oOptions,
oControl = oParentInfo && oParentInfo.control,
bHasTargetControl = (oControl || oOptions.controlId),
bIsValid = true,
sLogMessage = "";
if (!bHasTargetControl) {
sLogMessage = "The target " + oOptions._name + " has no controlId set and no parent so the target cannot be displayed.";
bIsValid = false;
}
if (!oOptions.controlAggregation) {
sLogMessage = "The target " + oOptions._name + " has a control id or a parent but no 'controlAggregation' was set, so the target could not be displayed.";
bIsValid = false;
}
if (sLogMessage) {
Log.error(sLogMessage, this);
}
return bIsValid || sLogMessage;
},
/**
* Refuses the target with the name <code>sName</code> by throwing an error asynchronously
*
* @param {string} sName The name of the target
* @param {string} sMessage The error message with more insights why the target is invalid
* @returns {Promise} The rejected promise
* @private
*/
_refuseInvalidTarget : function(sName, sMessage) {
return Promise.reject(new Error(sMessage + " - Target: " + sName));
}
};
});