UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

388 lines (336 loc) 14.1 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ './HashChangerBase' ], function(HashChangerBase) { "use strict"; /** * @class Class for manipulating and receiving changes of the relevant hash segment * which belongs to a router. This Class doesn't change the browser hash directly, * but informs its parent RouterHashChanger and finally changes the browser hash * through the {@link sap.ui.core.routing.HashChanger} * * @protected * @alias sap.ui.core.routing.RouterHashChanger */ var RouterHashChanger = HashChangerBase.extend("sap.ui.core.routing.RouterHashChanger", { constructor : function(mSettings) { if (!mSettings || !mSettings.parent) { throw new Error("sap.ui.core.routing.RouterHashChanger can't be instantiated without a parent"); } this.parent = mSettings.parent; // if no hash is given in mSettings, the default value should be "" this.hash = mSettings.hash || ""; this.subHashMap = mSettings.subHashMap; this.key = mSettings.key || ""; HashChangerBase.apply(this); } }); RouterHashChanger.InvalidHash = Object.create(null); RouterHashChanger.prototype.init = function() { this.parent.init(); }; RouterHashChanger.prototype._generatePrefixedKey = function(sKey) { return this.key ? (this.key + "-" + sKey) : sKey; }; /* * @param {string} sKey the prefix for the sub RouterHashChanger * @return {sap.ui.core.routing.RouterHashChanger} the sub RouterHashChanger * @protected */ RouterHashChanger.prototype.createSubHashChanger = function(sKey) { this.children = this.children || {}; var sPrefixedKey = this._generatePrefixedKey(sKey); if (this.children[sPrefixedKey]) { return this.children[sPrefixedKey]; } var oChild = new RouterHashChanger({ key: sPrefixedKey, parent: this, subHashMap: this.subHashMap, hash: (this.subHashMap && this.subHashMap[sPrefixedKey]) || "" }); oChild.attachEvent("hashSet", this._onChildHashChanged.bind(this, sPrefixedKey)); oChild.attachEvent("hashReplaced", this._onChildHashChanged.bind(this, sPrefixedKey)); this.children[sPrefixedKey] = oChild; return oChild; }; /** * Save the given hash and potentially fires a "hashChanged" event; may be extended to modify the hash before firing * the event. * * @param {string} sHash the new hash of the browser * @param {object} oSubHashMap the prefixes and hashes for the child RouterHashChangers * @param {boolean} bUpdateHashOnly if this parameter is set to true, the given sHash is saved in the instance but * no "hashChanged" event is fired. * @protected */ RouterHashChanger.prototype.fireHashChanged = function(sHash, oSubHashMap, bUpdateHashOnly) { var aKeys, sOldHash = this.hash; this.hash = sHash; this.subHashMap = oSubHashMap; if (!bUpdateHashOnly && sHash !== sOldHash) { this.fireEvent("hashChanged", { newHash : sHash, oldHash : sOldHash }); } if (this.children) { aKeys = Object.keys(this.children); aKeys.forEach(function(sChildKey) { var sChildHash = (oSubHashMap[sChildKey] === undefined ? "" : oSubHashMap[sChildKey]); this.children[sChildKey].fireHashChanged(sChildHash, oSubHashMap, bUpdateHashOnly); }.bind(this)); } }; RouterHashChanger.prototype._onChildHashChanged = function(sKey, oEvent) { var sChildKey = oEvent.getParameter("key") || sKey, sHash = oEvent.getParameter("hash"), aNestedHashInfo = oEvent.getParameter("nestedHashInfo"), aDeletePrefix = oEvent.getParameter("deletePrefix"); if (this._bCollectMode) { // collect the hash this._collectHash(sChildKey, sHash, aDeletePrefix); } else { this.fireEvent(oEvent.getId(), { hash: sHash, key: sChildKey, nestedHashInfo: aNestedHashInfo, deletePrefix: aDeletePrefix }); } }; RouterHashChanger.prototype._collectHash = function(sKey, sHash, aDeletePrefix) { this._aCollectedHashInfo = this._aCollectedHashInfo || []; this._aCollectedHashInfo.push({ key: sKey, hash: sHash, deletePrefix: aDeletePrefix }); }; RouterHashChanger.prototype._hasRouterAttached = function() { return this.hasListeners("hashChanged"); }; RouterHashChanger.prototype._collectActiveDescendantPrefix = function() { if (this.children) { var aKeys = Object.keys(this.children); return aKeys.reduce(function(aPrefix, sKey) { var oChild = this.children[sKey]; if (oChild._hasRouterAttached()) { // oChild is active aPrefix.push(sKey); Array.prototype.push.apply(aPrefix, oChild._collectActiveDescendantPrefix()); } return aPrefix; }.bind(this), []); } else { return []; } }; /** * Gets the current hash * * @return {string} the current hash * @protected */ RouterHashChanger.prototype.getHash = function() { if (this._isUnderCollectMode()) { // If one ancestor of the current RouterHashChanger is in the collect mode, // this function needs to return an invalid hash marker to prevent the Router // from parsing the hash because a hashChange event will be fired immediately // after the collect mode which contains the correct hash for the Router return RouterHashChanger.InvalidHash; } else { return this.hash; } }; /** * In case a RouterHashChanger is shared by multiple routers, the router that is latest initialized is saved as the * active router in the RouterHashChanger in order to allow it to do more actions (such as resetHash) to avoid the * change from affecting the other routers. * * @param {sap.ui.core.routing.Router} oRouter the router that should be saved as the active router * @returns {this} The 'this' instance to chain the call * @private */ RouterHashChanger.prototype._setActiveRouter = function(oRouter) { if (oRouter.getHashChanger() === this) { this._oActiveRouter = oRouter; } return this; }; /** * Reset the hash if the given router is the active router that is saved in this RouterHashChanger * * This is needed for allowing to fire the hashChanged event with the previous hash again * after displaying a Target without involving a Router. * * @param {sap.ui.core.routing.Router} oRouter the router from which the resetHash is started * @return {this} The current RouterHashChanger for chaining the method * @protected */ RouterHashChanger.prototype.resetHash = function(oRouter) { if (oRouter && this._oActiveRouter === oRouter) { this.hash = undefined; } return this; }; /** * Sets the hash to a certain value. When using this function, a browser history entry is written. * If you do not want to have an entry in the browser history, please use the {@link #replaceHash} function. * * @param {string} sHash New hash * @param {Promise} [pNestedHashChange] When this parameter is given, this RouterHashChanger switchs to collect * mode and all hash changes from its children will be collected. When this promise resolves, this * RouterHashChanger fires a "hashSet" event with its own hash and the hashes which are collected from the child * RouterHashChanger(s). * @param {boolean} [bSuppressActiveHashCollect=false] Whether this RouterHashChanger shouldn't collect the prefixes * from its active child RouterHashChanger(s) and forward them as delete prefixes within the next "hashSet" event * @returns {Promise|undefined} When <code>pNestedHashChange</code> is given as a Promise, this function also returns * a Promise which resolves after the given promise resolves. Otherwise it returns <code>undefined</code>. * @protected */ RouterHashChanger.prototype.setHash = function(sHash, pNestedHashChange, bSuppressActiveHashCollect) { if (!(pNestedHashChange instanceof Promise)) { bSuppressActiveHashCollect = pNestedHashChange; pNestedHashChange = null; } return this._modifyHash(sHash, pNestedHashChange, bSuppressActiveHashCollect); }; /** * Replaces the hash with a certain value. When using the replace function, no browser history entry is written. * If you want to have an entry in the browser history, please use the {@link #setHash} function. * * @param {string} sHash New hash * @param {sap.ui.core.routing.HistoryDirection} sDirection The direction information for the hash replacement * @param {Promise} [pNestedHashChange] When this parameter is given, this RouterHashChanger switchs to collect * mode and all hash changes from its children will be collected. When this promise resolves, this * RouterHashChanger fires a "hashReplaced" event with its own hash and the hashes which are collected from the child * RouterHashChanger(s). * @param {boolean} [bSuppressActiveHashCollect=false] Whether this RouterHashChanger shouldn't collect the prefixes * from its active child RouterHashChanger(s) and forward them as delete prefixes within the next "hashReplaced" event * @returns {Promise|undefined} When <code>pNestedHashChange</code> is given as a Promise, this function also returns * a Promise which resolves after the given promise resolves. Otherwise it returns <code>undefined</code>. * @protected */ RouterHashChanger.prototype.replaceHash = function(sHash, sDirection, pNestedHashChange, bSuppressActiveHashCollect) { if (typeof sDirection !== "string") { bSuppressActiveHashCollect = pNestedHashChange; pNestedHashChange = sDirection; sDirection = undefined; } if (!(pNestedHashChange instanceof Promise)) { bSuppressActiveHashCollect = pNestedHashChange; pNestedHashChange = null; } return this._modifyHash(sHash, pNestedHashChange, bSuppressActiveHashCollect, /* bReplace */true, sDirection); }; /** * Collects all hash changes from the nested RouterHashChanger(s) when <code>pNestedHashChange</code> is given as a * Promise. After the given promise resolves, it fires a "hashSet" or "hashReplaced" event depending on the * <code>bReplace</code> parameter. * * If the <code>pNestedHashChange</code> isn't given as a Promise. The function fires the "hashSet" or * "hashReplaced" event directly * * @param {string} sHash New hash * @param {Promise} [pNestedHashChange] When this parameter is given, this RouterHashChanger switchs to collect * mode and all hash changes from its children will be collected. When this promise resolves, this * RouterHashChanger fires a "hashSet" or "hashReplaced" event with its own hash and the hashes which are collected * from the child RouterHashChanger(s). * @param {boolean} [bSuppressActiveHashCollect=false] Whether this RouterHashChanger shouldn't collect the prefixes * from its active child RouterHashChanger(s) and forward them as delete prefixes within the next "hashReplaced" event * @param {boolean} [bReplace=false] Whether a "hashReplace" or "hashSet" event should be fired at the end * @param {sap.ui.core.routing.HistoryDirection} sDirection The direction information for the hash replacement. * This is set only when the parameter <code>bReplace</code> is set to <code>true</code>. * @returns {Promise|undefined} When <code>pNestedHashChange</code> is given as a Promise, this function also returns * a Promise which resolves after the given promise resolves. Otherwise it returns <code>undefined</code>. * * @private */ RouterHashChanger.prototype._modifyHash = function(sHash, pNestedHashChange, bSuppressActiveHashCollect, bReplace, sDirection) { var sEventName = bReplace ? "hashReplaced" : "hashSet", that = this, oParams = { hash: sHash }; if (bReplace && sDirection) { oParams.direction = sDirection; } if (!bSuppressActiveHashCollect) { oParams.deletePrefix = this._collectActiveDescendantPrefix(); } if (pNestedHashChange) { this._bCollectMode = true; return pNestedHashChange.then(function() { oParams.nestedHashInfo = that._aCollectedHashInfo; // fire hashSet or hashReplaced event with the collected info that.fireEvent(sEventName, oParams); // reset collected hash info and exit collect mode that._aCollectedHashInfo = null; that._bCollectMode = false; }); } else { // fire hashSet or hashReplaced event this.fireEvent(sEventName, oParams); } }; /* * Checks whether one of its ancestors is currently in collect mode * * @returns {boolean} whether one of the ancestors is in collect mode * * @private */ RouterHashChanger.prototype._isUnderCollectMode = function() { // check whether one of its ancestors (RouterHashChanger) is in collect mode return this.parent instanceof RouterHashChanger && this.parent._isInCollectMode(); }; /** * Checks whether the RouterHashChanger or one of its ancestors is in collect mode * * @returns {boolean} whether the RouterHashChanger or one of its ancestors is in collect mode * * @private */ RouterHashChanger.prototype._isInCollectMode = function() { return this._bCollectMode || (this.parent instanceof RouterHashChanger && this.parent._isInCollectMode()); }; RouterHashChanger.prototype.destroy = function() { this.parent.deregisterRouterHashChanger(this); if (this.children) { Object.keys(this.children).forEach(function(sKey) { var oChild = this.children[sKey]; oChild.destroy(); }.bind(this)); delete this.children; } delete this.hash; delete this.subHashMap; delete this.parent; delete this.key; HashChangerBase.prototype.destroy.apply(this, arguments); }; /** * Removes the given RouterHashChanger from this instance as child * * @param {sap.ui.core.routing.RouterHashChanger} oRouterHashChanger the RouterHashChanger which is going to be removed * @private */ RouterHashChanger.prototype.deregisterRouterHashChanger = function(oRouterHashChanger) { if (this.children) { Object.keys(this.children).some(function(sKey) { var oChild = this.children[sKey]; if (oChild === oRouterHashChanger) { delete this.children[sKey]; return true; } }.bind(this)); } }; return RouterHashChanger; });