@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
452 lines (387 loc) • 15.4 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",
"./RouterHashChanger",
'sap/ui/thirdparty/hasher',
"sap/base/Log",
"sap/base/util/ObjectPath",
"sap/ui/performance/trace/Interaction"
], function(HashChangerBase, RouterHashChanger, hasher, Log, ObjectPath, Interaction) {
"use strict";
/**
* @class Class for manipulating and receiving changes of the browser hash with the hasher framework.
*
* Fires a <code>hashChanged</code> event if the browser hash changes.
* @extends sap.ui.core.routing.HashChangerBase
*
* @public
* @alias sap.ui.core.routing.HashChanger
*/
var HashChanger = HashChangerBase.extend("sap.ui.core.routing.HashChanger", {
constructor : function() {
HashChangerBase.apply(this);
}
});
/**
* Will start listening to hashChanges with the parseHash function.
* This will also fire a hashchanged event with the initial hash.
*
* @public
* @return {boolean} false if it was initialized before, true if it was initialized the first time
*/
HashChanger.prototype.init = function() {
if (this._initialized) {
Log.info("this HashChanger instance has already been initialized.");
return false;
}
this._initialized = true;
hasher.changed.add(this.fireHashChanged, this); //parse hash changes
if (!hasher.isActive()) {
hasher.initialized.addOnce(this.fireHashChanged, this); //parse initial hash
hasher.init(); //start listening for history change
} else {
this.fireHashChanged(hasher.getHash());
}
return this._initialized;
};
/**
* Fires the hashchanged event, may be extended to modify the hash before fireing the event
* @param {string} sNewHash the new hash of the browser
* @param {string} sOldHash - the previous hash
* @protected
*/
HashChanger.prototype.fireHashChanged = function(sNewHash, sOldHash) {
this.fireEvent("hashChanged", {
newHash: sNewHash,
oldHash: sOldHash
});
};
/**
* Creates an instance of {@link sap.ui.core.routing.RouterHashChanger} which is connected with
* this HashChanger.
*
* The HashChanger attaches to the RouterHashChanger's "hashSet" and "hashReplaced" events to
* propagate the hash modification from the RouterHashChanger to the browser.
*
* There's maximum one instance of RouterHashChanger created under a HashChanger.
*
* @return {sap.ui.core.routing.RouterHashChanger} the created RouterHashChanger
* @private
*/
HashChanger.prototype.createRouterHashChanger = function() {
if (!this._oRouterHashChanger) {
var oParsedHash = this._parseHash(this.getHash());
this._oRouterHashChanger = new RouterHashChanger({
parent: this,
hash: oParsedHash.hash,
subHashMap: oParsedHash.subHashMap
});
this._registerListenerToRelevantEvents();
this._oRouterHashChanger.attachEvent("hashSet", this._onHashModified, this);
this._oRouterHashChanger.attachEvent("hashReplaced", this._onHashModified, this);
}
this._oRouterHashChanger.attachEvent("hashChanged", function() {
Interaction.notifyNavigation();
});
return this._oRouterHashChanger;
};
HashChanger.prototype._registerListenerToRelevantEvents = function() {
if (!this._mEventListeners) {
this._mEventListeners = {};
// get all relevant events which should be forwarded to the _oRouterHashChanger
this.getRelevantEventsInfo().forEach(function(oEventInfo) {
var sEventName = oEventInfo.name,
fnListener = this._onHashChangedForRouterHashChanger.bind(this, oEventInfo);
this._mEventListeners[sEventName] = fnListener;
this.attachEvent(sEventName, fnListener, this);
}.bind(this));
}
};
HashChanger.prototype._deregisterListenerFromRelevantEvents = function() {
if (this._mEventListeners) {
var aEventNames = Object.keys(this._mEventListeners);
aEventNames.forEach(function(sEventName) {
this.detachEvent(sEventName, this._mEventListeners[sEventName], this);
}.bind(this));
delete this._mEventListeners;
}
};
HashChanger.prototype._onHashChangedForRouterHashChanger = function(oEventInfo, oEvent) {
if (this._oRouterHashChanger) {
var oParamMapping = oEventInfo.paramMapping || {},
sParamName = oParamMapping["newHash"] || "newHash",
sNewHash = oEvent.getParameter(sParamName) || "",
oParsedHash = this._parseHash(sNewHash);
this._oRouterHashChanger.fireHashChanged(oParsedHash.hash, oParsedHash.subHashMap, !!oEventInfo.updateHashOnly);
}
};
HashChanger.prototype._onHashModified = function(oEvent) {
var sEventName = oEvent.getId(),
aHashes = [oEvent.getParameter("hash")],
aKeys = [oEvent.getParameter("key")],
aNestedHashInfo = oEvent.getParameter("nestedHashInfo"),
aDeletePrefix = oEvent.getParameter("deletePrefix") || [];
if (Array.isArray(aNestedHashInfo)) {
aNestedHashInfo.forEach(function(oHashInfo) {
aHashes.push(oHashInfo.hash);
aKeys.push(oHashInfo.key);
if (Array.isArray(oHashInfo.deletePrefix)) {
oHashInfo.deletePrefix.forEach(function(sDeletePrefix) {
if (aDeletePrefix.indexOf(sDeletePrefix) === -1) {
aDeletePrefix.push(sDeletePrefix);
}
});
}
});
}
if (sEventName === "hashSet") {
this._setSubHash(aKeys, aHashes, aDeletePrefix);
} else {
this._replaceSubHash(aKeys, aHashes, aDeletePrefix);
}
};
HashChanger.prototype._setSubHash = function(aKeys, aSubHashes, aChildPrefix) {
// construct the full hash by replacing the part starts with the sKey
var sHash = this._reconstructHash(aKeys, aSubHashes, aChildPrefix);
this.setHash(sHash);
};
HashChanger.prototype._replaceSubHash = function(aKeys, aSubHashes, aChildPrefix) {
// construct the full hash by replacing the part starts with the sKey
var sHash = this._reconstructHash(aKeys, aSubHashes, aChildPrefix);
this.replaceHash(sHash);
};
/**
* Reconstructs the hash
*
* @param {string[]} aKeys The prefixes of the RouterHashChangers which changed their hash during the last navTo call
* @param {string[]} aValues The new hashes in the last navTo call
* @param {string[]} aDeleteKeys The prefixes of the RouterHashChanger which are navigated away and their hashes will be deleted from the browser hash
* @returns {string} The reconstructed hash
* @private
*/
HashChanger.prototype._reconstructHash = function(aKeys, aValues, aDeleteKeys) {
var aParts = this.getHash().split("&/"),
sTopHash = aParts.shift();
aKeys.forEach(function(sKey, index) {
// remove sKey from aDeleteKeys because sKey should have a part in the final browser hash
// when sValue is falsy, the sKey will be inserted into aDeleteKeys later
if (aDeleteKeys) {
aDeleteKeys = aDeleteKeys.filter(function(sDeleteKey) {
return sDeleteKey !== sKey;
});
}
var sValue = aValues[index];
if (sKey === undefined) {
// change the top level hash
// convert all values to string for compatibility reason (for
// example, undefined is converted to "undefined")
sTopHash = sValue + "";
} else {
var bFound = aParts.some(function(sPart, i, aParts) {
if (sPart.startsWith(sKey)) {
if (sValue) {
// replace the subhash
aParts[i] = sKey + "/" + sValue;
} else {
// remove the subhash
aDeleteKeys.push(sKey);
}
return true;
}
});
if (!bFound) {
// the subhash must be added
aParts.push(sKey + "/" + sValue);
}
}
});
if (aDeleteKeys && aDeleteKeys.length > 0) {
// remove dependent subhashes from aDeleteKeys from the hash
aParts = aParts.filter(function(sPart) {
return !aDeleteKeys.some(function(sPrefix) {
return sPart.startsWith(sPrefix);
});
});
}
aParts.unshift(sTopHash);
return aParts.join("&/");
};
HashChanger.prototype._parseHash = function(sHash) {
var aParts = sHash.split("&/");
return {
hash: aParts.shift(),
subHashMap: aParts.reduce(function(oMap, sPart) {
var iSlashPos = sPart.indexOf("/");
if (iSlashPos === -1) {
oMap[sPart] = "";
} else {
oMap[sPart.substring(0, iSlashPos)] = sPart.substring(iSlashPos + 1);
}
return oMap;
}, {})
};
};
/**
* 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
* @public
*/
HashChanger.prototype.setHash = function(sHash) {
HashChangerBase.prototype.setHash.apply(this, arguments);
hasher.setHash(sHash);
};
/**
* 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
* @public
*/
HashChanger.prototype.replaceHash = function(sHash) {
HashChangerBase.prototype.replaceHash.apply(this, arguments);
hasher.replaceHash(sHash);
};
/**
* Gets the current hash
*
* @return {string} the current hash
* @public
*/
HashChanger.prototype.getHash = function() {
return hasher.getHash();
};
/**
* @typedef {object} sap.ui.core.routing.HashChangerEventInfo
* @description The object containing the event info for the events that are forwarded to {@link sap.ui.core.routing.RouterHashChanger}.
* @property {string} name The name of the event that is fired by the HashChanger and should be forwarded to the RouterHashChanger
* @property {sap.ui.core.routing.HashChangerEventParameterMapping} [paramMapping] The optional defined parameter name mapping that is
* used for forwarding the event to the {@link sap.ui.core.routing.RouterHashChanger}.
* @property {boolean} updateHashOnly Indicates whether the event is ignored by every RouterHashChanger
* instance and is only relevant for the other routing classes, for example {@link sap.ui.core.routing.History}.
* @protected
* @since 1.82.0
*/
/**
* @typedef {object} sap.ui.core.routing.HashChangerEventParameterMapping
* @description The object containing the parameter mapping for forwarding the event to the {@link sap.ui.core.routing.RouterHashChanger}.
* @property {string} [newHash] The name of the parameter whose value is used as the <code>newHash</code> parameter
* in the event that is forwarded to the {@link sap.ui.core.routing.RouterHashChanger}. If this isn't set, the
* value is taken from the property <code>newHash</code>.
* @property {string} [oldHash] The name of the parameter whose value is used as the <code>oldHash</code> parameter
* in the event that is forwarded to the {@link sap.ui.core.routing.RouterHashChanger}. If this isn't set, the
* value is taken from the property <code>oldHash</code>.
* @property {string} [fullHash] The name of the parameter whose value is used as the <code>fullHash</code> parameter
* in the event that is forwarded to the {@link sap.ui.core.routing.RouterHashChanger}. If this isn't set, the
* value is taken from the property <code>fullHash</code>.
* @protected
* @since 1.82.0
*/
/**
* Defines the events and its parameters which should be used for tracking the hash changes
*
* @return {sap.ui.core.routing.HashChangerEventInfo[]} The array containing the events info
* @protected
*/
HashChanger.prototype.getRelevantEventsInfo = function() {
return [
{
name: "hashChanged",
paramMapping: {
fullHash: "newHash"
}
}
];
};
/**
* Cleans the event registration
* @see sap.ui.base.Object.prototype.destroy
* @protected
*/
HashChanger.prototype.destroy = function() {
if (this._oRouterHashChanger) {
this._deregisterListenerFromRelevantEvents();
this._oRouterHashChanger.destroy();
this._oRouterHashChanger = undefined;
}
delete this._initialized;
hasher.changed.remove(this.fireHashChanged, this);
HashChangerBase.prototype.destroy.apply(this, arguments);
};
HashChanger.prototype.deregisterRouterHashChanger = function() {
// detach the hashChanged event handler for the RouterHashChanger instance
this._deregisterListenerFromRelevantEvents();
delete this._oRouterHashChanger;
};
(function() {
var _oHashChanger = null;
/**
* Gets a global singleton of the HashChanger. The singleton will get created when this function is invoked for the first time.
* @public
* @return {sap.ui.core.routing.HashChanger} The global HashChanger
* @static
*/
HashChanger.getInstance = function() {
if (!_oHashChanger) {
_oHashChanger = new HashChanger();
}
return _oHashChanger;
};
function extendHashChangerEvents (oHashChanger) {
var sEventName,
aExistingEventListeners,
aNewEventListeners;
for (sEventName in _oHashChanger.mEventRegistry) {
// only modify the new event registry if the old one contains the entry
if (_oHashChanger.mEventRegistry.hasOwnProperty(sEventName)) {
aExistingEventListeners = _oHashChanger.mEventRegistry[sEventName];
aNewEventListeners = oHashChanger.mEventRegistry[sEventName];
if (aNewEventListeners) {
// Both instances have the same event: merge the arrays
oHashChanger.mEventRegistry[sEventName] = aExistingEventListeners.concat(aNewEventListeners);
} else {
// Only the previous hashchanger has the event - add it to the new hashCHanger
oHashChanger.mEventRegistry[sEventName] = aExistingEventListeners;
}
}
}
}
/**
* Sets the hashChanger to a new instance, destroys the old one and copies all its event listeners to the new one
* @param {sap.ui.core.routing.HashChanger} oHashChanger the new instance for the global singleton
* @protected
*/
HashChanger.replaceHashChanger = function(oHashChanger) {
if (_oHashChanger && oHashChanger) {
var fnGetHistoryInstance = ObjectPath.get("sap.ui.core.routing.History.getInstance"),
oHistory;
// replace the hash changer on oHistory should occur before the replacement on router hash changer
// because the history direction should be determined before a router processes the hash.
if (fnGetHistoryInstance) {
oHistory = fnGetHistoryInstance();
// set the new hash changer to oHistory. This will also deregister the listeners from the old hash
// changer.
oHistory._setHashChanger(oHashChanger);
}
if (_oHashChanger._oRouterHashChanger) {
_oHashChanger._oRouterHashChanger.detachEvent("hashSet", _oHashChanger._onHashModified, _oHashChanger);
_oHashChanger._oRouterHashChanger.detachEvent("hashReplaced", _oHashChanger._onHashModified, _oHashChanger);
_oHashChanger._deregisterListenerFromRelevantEvents();
oHashChanger._oRouterHashChanger = _oHashChanger._oRouterHashChanger;
oHashChanger._oRouterHashChanger.parent = oHashChanger;
delete _oHashChanger._oRouterHashChanger;
oHashChanger._oRouterHashChanger.attachEvent("hashSet", oHashChanger._onHashModified, oHashChanger);
oHashChanger._oRouterHashChanger.attachEvent("hashReplaced", oHashChanger._onHashModified, oHashChanger);
oHashChanger._registerListenerToRelevantEvents();
}
extendHashChangerEvents(oHashChanger);
_oHashChanger.destroy();
}
_oHashChanger = oHashChanger;
};
}());
return HashChanger;
});