@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
388 lines (336 loc) • 14.1 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.
*/
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;
});