UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

391 lines (333 loc) 13.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. */ /*global Promise*/ sap.ui.define(['sap/m/InstanceManager', 'sap/m/NavContainer', 'sap/m/SplitContainer', 'sap/ui/base/Object', 'sap/ui/core/routing/History', 'sap/ui/Device', "sap/base/Log"], function(InstanceManager, NavContainer, SplitContainer, BaseObject, History, Device, Log) { "use strict"; var oOnAfterShowDelegate = { "onAfterShow": function(oEvent) { // 'this' == current page / view // 'parent' == navContainer this.getParent().hidePlaceholder({}); this.removeEventDelegate(oOnAfterShowDelegate); } }; /** * Constructor for a new <code>TargetHandler</code>. * * @class * Used for closing dialogs and showing transitions in <code>NavContainers</code> * when targets are displayed. * * <b>Note:</b> You should not create an own instance of this class. It is created * when using <code>{@link sap.m.routing.Router}</code> or <code>{@link sap.m.routing.Targets}</code>. * * <b>Note:</b> You may use the <code>{@link #setCloseDialogs}</code> function to specify if dialogs should be * closed on displaying other views. The dialogs are closed when a different target is displayed than the * previously displayed one, otherwise the dialogs are kept open. * * @param {boolean} closeDialogs Closes all open dialogs before navigating to a different target, if set to * <code>true</code> (default). If set to <code>false</code>, it will just navigate without closing dialogs. * @public * @since 1.28.1 * @alias sap.m.routing.TargetHandler */ var TargetHandler = BaseObject.extend("sap.m.routing.TargetHandler", { constructor : function (bCloseDialogs) { //until we reverse the order of events fired by router we need to queue handleRouteMatched this._aQueue = []; // The Promise object here is used to make the navigations in the same order as they are triggered, only for async this._oNavigationOrderPromise = Promise.resolve(); if (bCloseDialogs === undefined) { this._bCloseDialogs = true; } else { this._bCloseDialogs = !!bCloseDialogs; } } }); /* ================================= * public * =================================*/ /** * Sets if a navigation should close dialogs. * * <b>Note:</b> The dialogs are closed when a different target is displayed than the previous one, * otherwise the dialogs are kept open even when <code>bCloseDialogs</code> is <code>true</code>. * * @param {boolean} bCloseDialogs Close dialogs if <code>true</code> * @public * @returns {this} For chaining */ TargetHandler.prototype.setCloseDialogs = function (bCloseDialogs) { this._bCloseDialogs = !!bCloseDialogs; return this; }; /** * Gets if a navigation should close dialogs. * * @public * @returns {boolean} A flag indication if dialogs will be closed. */ TargetHandler.prototype.getCloseDialogs = function () { return this._bCloseDialogs; }; TargetHandler.prototype.addNavigation = function(oParameters) { this._aQueue.push(oParameters); }; TargetHandler.prototype.navigate = function(oDirectionInfo) { var aUniqueNavigations = this._groupNavigation(), aResultingNavigations = this._createResultingNavigations(oDirectionInfo.navigationIdentifier, aUniqueNavigations), bCloseDialogs = false, bBack = this._getDirection(oDirectionInfo), bNavigationOccurred; while (aResultingNavigations.length) { bNavigationOccurred = this._applyNavigationResult(aResultingNavigations.shift().oParams, bBack); bCloseDialogs = bCloseDialogs || bNavigationOccurred; } if (bCloseDialogs) { this._closeDialogs(); } }; /* ================================= * private * ================================= */ /** * This method is used to chain navigations to be triggered in the correct order, only relevant for async * @private */ TargetHandler.prototype._chainNavigation = function(fnNavigation, sNavigationIdentifier) { var oPromiseChain = this._oNavigationOrderPromise.then(fnNavigation); // navigation order promise should resolve even when the inner promise rejects to allow further navigation // to be done. Therefore it's needed to catch the rejected inner promise this._oNavigationOrderPromise = oPromiseChain.catch(function(oError) { Log.error("The following error occurred while displaying routing target with name '" + sNavigationIdentifier + "': " + oError); }); return oPromiseChain; }; /** * @private */ TargetHandler.prototype._getDirection = function(oDirectionInfo) { var iTargetLevel = oDirectionInfo.level, oHistory = History.getInstance(), bBack = false; if (oDirectionInfo.direction === "Backwards") { bBack = true; } else if (isNaN(iTargetLevel) || isNaN(this._iCurrentLevel) || iTargetLevel === this._iCurrentLevel) { if (oDirectionInfo.askHistory) { bBack = oHistory.getDirection() === "Backwards"; } } else { bBack = iTargetLevel < this._iCurrentLevel; } this._iCurrentLevel = iTargetLevel; return bBack; }; TargetHandler.prototype._groupNavigation = function() { var oCurrentParams, oCurrentContainer, sCurrentAggregation, oNavigationParams, aUniqueNavigations = [], i; while (this._aQueue.length) { oCurrentParams = this._aQueue.shift(); oCurrentContainer = oCurrentParams.targetControl; sCurrentAggregation = oCurrentParams.aggregationName; if (!oCurrentParams.preservePageInSplitContainer) { for (i = 0; i < aUniqueNavigations.length; i++) { oNavigationParams = aUniqueNavigations[i]; if (oCurrentContainer !== oNavigationParams.targetControl || sCurrentAggregation !== oNavigationParams.aggregationName) { continue; } aUniqueNavigations.splice(i, 1); break; } } aUniqueNavigations.push(oCurrentParams); } return aUniqueNavigations; }; /** * Goes through the queue and adds the last Transition for each container in the queue * In case of a navContainer or phone mode, only one transition for the container is allowed. * In case of a splitContainer in desktop mode, two transitions are allowed, one for the master and one for the detail. * Both transitions will be the same. * @returns {array} a queue of navigations * @private */ TargetHandler.prototype._createResultingNavigations = function(sNavigationIdentifier, aUniqueNavigations) { var i, oCurrentParams, oCurrentContainer, oCurrentNavigation, aResults = [], oView, bIsSplitContainer, bPreservePageInSplitContainer, oResult; while (aUniqueNavigations.length) { oCurrentParams = aUniqueNavigations.shift(); oCurrentContainer = oCurrentParams.targetControl; bIsSplitContainer = oCurrentContainer instanceof SplitContainer; oView = oCurrentParams.view; oCurrentNavigation = { oContainer : oCurrentContainer, oParams : oCurrentParams }; if (bIsSplitContainer) { oCurrentNavigation.bIsMasterPage = !!oCurrentContainer.getMasterPage(oView.getId()); } bPreservePageInSplitContainer = bIsSplitContainer && oCurrentParams.preservePageInSplitContainer && //only switch the page if the container has a page in this aggregation oCurrentContainer.getCurrentPage(oCurrentNavigation.bIsMasterPage) && sNavigationIdentifier !== oCurrentParams.navigationIdentifier; for (i = 0; i < aResults.length; i++) { oResult = aResults[i]; //The result targets a different container if (oResult.oContainer !== oCurrentContainer) { continue; } if (bIsSplitContainer) { // if its a splitContainer - in the mobile case it behaves like a nav container if (Device.system.phone) { aResults.splice(i, 1); break; } else if (oResult.bIsMasterPage === oCurrentNavigation.bIsMasterPage) { //The page is in the same aggregation - overwrite the previous transition //We have a desktop SplitContainer and need to add to transitions if necessary if (!bPreservePageInSplitContainer) { aResults.splice(i, 1); } break; } } } //A new Nav container was found if (!bPreservePageInSplitContainer) { aResults.push(oCurrentNavigation); } } return aResults; }; /** * Triggers all navigation on the correct containers with the transition direction. * * @param {object} oParams the navigation parameters * @param {boolean} bBack forces the nav container to show a backwards transition * @private * @returns {boolean} if a navigation occured - if the page is already displayed false is returned */ TargetHandler.prototype._applyNavigationResult = function(oParams, bBack) { var oTargetControl = oParams.targetControl, oPreviousPage, //Parameters for the nav Container oArguments = oParams.eventData, //Nav container does not work well if you pass undefined as transition sTransition = oParams.placeholderShown ? "show" : (oParams.transition || ""), oTransitionParameters = oParams.transitionParameters, sViewId = oParams.view && oParams.view.getId(), //this is only necessary if the target control is a Split container since the nav container only has a pages aggregation bNextPageIsMaster = oTargetControl instanceof SplitContainer && !!oTargetControl.getMasterPage(sViewId), bNavigationRelevant = (oTargetControl instanceof SplitContainer || oTargetControl instanceof NavContainer) && oParams.view, bPlaceholderAutoClose, oPlaceholderContainer; if (oParams.placeholderConfig) { bPlaceholderAutoClose = oParams.placeholderConfig.autoClose; oPlaceholderContainer = oParams.placeholderConfig.container; } if (!bNavigationRelevant) { if (oPlaceholderContainer && bPlaceholderAutoClose && oPlaceholderContainer.hidePlaceholder) { oPlaceholderContainer.hidePlaceholder(oParams.placeholderConfig); } return false; } // It's NOT needed to navigate when both of the following conditions are valid: // 1. The target control is already rendered // 2. The target control already has the target view as the current page // // This fix the problem that the route parameters can't be forwarded to the initial page's onBeforeShow event. // In this case, the 'to' method of target control has to be explicitly called to pass the route parameters for the // onBeforeShow event which is fired in the onBeforeRendering of the target control. // // TODO: when target view is loaded asyncly, it could happen that the target control is rendered with empty content and // the target view is added later. oTargetControl.getDomRef has to be adapted with some new method in target control. if (oTargetControl.getDomRef() && oTargetControl.getCurrentPage(bNextPageIsMaster).getId() === sViewId) { if (bPlaceholderAutoClose && oPlaceholderContainer && oPlaceholderContainer.hidePlaceholder) { oPlaceholderContainer.hidePlaceholder(oParams.placeholderConfig); } Log.info("navigation to view with id: " + sViewId + " is skipped since it already is displayed by its targetControl", "sap.m.routing.TargetHandler"); return false; } else if (bPlaceholderAutoClose) { oParams.view.addEventDelegate(oOnAfterShowDelegate, oParams.view); } Log.info("navigation to view with id: " + sViewId + " the targetControl is " + oTargetControl.getId() + " backwards is " + bBack); if (bBack) { // insert previous page if not in nav container yet oPreviousPage = oTargetControl.getPreviousPage(bNextPageIsMaster); if (!oPreviousPage || oPreviousPage.getId() !== sViewId) { oTargetControl.insertPreviousPage(sViewId, sTransition , oArguments); } oTargetControl.backToPage(sViewId, oArguments, oTransitionParameters); } else { oTargetControl.to(sViewId, sTransition, oArguments, oTransitionParameters); } return true; }; /** * Closes all dialogs if the closeDialogs property is set to true. * * @private */ TargetHandler.prototype._closeDialogs = function() { if (!this._bCloseDialogs) { return; } // close open popovers if (InstanceManager.hasOpenPopover()) { InstanceManager.closeAllPopovers(); } // close open dialogs if (InstanceManager.hasOpenDialog()) { InstanceManager.closeAllDialogs(); } // close open LightBoxes if (InstanceManager.hasOpenLightBox()) { InstanceManager.closeAllLightBoxes(); } }; /** * Calls the 'showPlaceholder' method of the respective target container control depending on whether * a placeholder is needed or not. * * @param {object} mSettings Object containing the container control and the view object to display * @param {sap.ui.core.Control} mSettings.container The navigation target container * @param {sap.ui.core.Control|Promise} mSettings.object The component/view object * @return {Promise} Promise that resolves after the placeholder is loaded * * @private * @ui5-restricted sap.ui.core.routing */ TargetHandler.prototype.showPlaceholder = function(mSettings) { var oContainer = mSettings.container, bNeedsPlaceholder = true, oObject; if (mSettings.object && !(mSettings.object instanceof Promise)) { oObject = mSettings.object; } if (mSettings.container && typeof mSettings.container.needPlaceholder === "function") { bNeedsPlaceholder = mSettings.container.needPlaceholder(mSettings.aggregation, oObject); } if (bNeedsPlaceholder) { return oContainer.showPlaceholder(mSettings); } else { return Promise.resolve(); } }; return TargetHandler; });