Path: blob/master/webroot/rsrc/externals/javelin/lib/History.js
12242 views
/**1* @requires javelin-stratcom2* javelin-install3* javelin-uri4* javelin-util5* @provides javelin-history6* @javelin7*/89/**10* JX.History provides a stable interface for managing the browser's history11* stack. Whenever the history stack mutates, the "history:change" event is12* invoked via JX.Stratcom.13*14* Inspired by History Manager implemented by Christoph Pojer (@cpojer)15* @see https://github.com/cpojer/mootools-history16*/17JX.install('History', {1819statics : {2021// Mechanisms to @{JX.History.install} with (in preferred support order).22// The default behavior is to use the best supported mechanism.23DEFAULT : Infinity,24PUSHSTATE : 3,25HASHCHANGE : 2,26POLLING : 1,2728// Last path parsed from the URL fragment.29_hash : null,3031// Some browsers fire an extra "popstate" on initial page load, so we keep32// track of the initial path to normalize behavior (and not fire the extra33// event).34_initialPath : null,3536// Mechanism used to interface with the browser history stack.37_mechanism : null,3839/**40* Starts history management. This method must be invoked first before any41* other JX.History method can be used.42*43* @param int An optional mechanism used to interface with the browser44* history stack. If it is not supported, the next supported45* mechanism will be used.46*/47install : function(mechanism) {48if (__DEV__) {49if (JX.History._installed) {50JX.$E('JX.History.install(): can only install once.');51}52JX.History._installed = true;53}5455mechanism = mechanism || JX.History.DEFAULT;5657if (mechanism >= JX.History.PUSHSTATE && 'pushState' in history) {58JX.History._mechanism = JX.History.PUSHSTATE;59JX.History._initialPath = JX.History._getBasePath(location.href);60JX.Stratcom.listen('popstate', null, JX.History._handleChange);61} else if (mechanism >= JX.History.HASHCHANGE &&62'onhashchange' in window) {63JX.History._mechanism = JX.History.HASHCHANGE;64JX.Stratcom.listen('hashchange', null, JX.History._handleChange);65} else {66JX.History._mechanism = JX.History.POLLING;67setInterval(JX.History._handleChange, 200);68}69},7071/**72* Get the name of the mechanism used to interface with the browser73* history stack.74*75* @return string Mechanism, either pushstate, hashchange, or polling.76*/77getMechanism : function() {78if (__DEV__) {79if (!JX.History._installed) {80JX.$E(81'JX.History.getMechanism(): ' +82'must call JX.History.install() first.');83}84}85return JX.History._mechanism;86},8788/**89* Returns the path on top of the history stack.90*91* If the HTML5 History API is unavailable and an eligible path exists in92* the current URL fragment, the fragment is parsed for a path. Otherwise,93* the current URL path is returned.94*95* @return string Path on top of the history stack.96*/97getPath : function() {98if (__DEV__) {99if (!JX.History._installed) {100JX.$E(101'JX.History.getPath(): ' +102'must call JX.History.install() first.');103}104}105if (JX.History.getMechanism() === JX.History.PUSHSTATE) {106return JX.History._getBasePath(location.href);107} else {108var parsed = JX.History._parseFragment(location.hash);109return parsed || JX.History._getBasePath(location.href);110}111},112113/**114* Pushes a path onto the history stack.115*116* @param string Path.117* @param wild State object for History API.118* @return void119*/120push : function(path, state) {121if (__DEV__) {122if (!JX.History._installed) {123JX.$E(124'JX.History.push(): ' +125'must call JX.History.install() first.');126}127}128if (JX.History.getMechanism() === JX.History.PUSHSTATE) {129if (JX.History._initialPath && JX.History._initialPath !== path) {130JX.History._initialPath = null;131}132history.pushState(state || null, null, path);133JX.History._fire(path, state);134} else {135location.hash = JX.History._composeFragment(path);136}137},138139/**140* Modifies the path on top of the history stack.141*142* @param string Path.143* @return void144*/145replace : function(path) {146if (__DEV__) {147if (!JX.History._installed) {148JX.$E(149'JX.History.replace(): ' +150'must call JX.History.install() first.');151}152}153if (JX.History.getMechanism() === JX.History.PUSHSTATE) {154history.replaceState(null, null, path);155JX.History._fire(path);156} else {157var uri = JX.$U(location.href);158uri.setFragment(JX.History._composeFragment(path));159// Safari bug: "location.replace" does not respect changes made via160// setting "location.hash", so use "history.replaceState" if possible.161if ('replaceState' in history) {162history.replaceState(null, null, uri.toString());163JX.History._handleChange();164} else {165location.replace(uri.toString());166}167}168},169170_handleChange : function(e) {171var path = JX.History.getPath();172var state = (e && e.getRawEvent().state);173174if (JX.History.getMechanism() === JX.History.PUSHSTATE) {175if (path === JX.History._initialPath) {176JX.History._initialPath = null;177} else {178JX.History._fire(path, state);179}180} else {181if (path !== JX.History._hash) {182JX.History._hash = path;183JX.History._fire(path);184}185}186},187188_fire : function(path, state) {189JX.Stratcom.invoke('history:change', null, {190path: JX.History._getBasePath(path),191state: state192});193},194195_getBasePath : function(href) {196return JX.$U(href).setProtocol(null).setDomain(null).toString();197},198199_composeFragment : function(path) {200path = JX.History._getBasePath(path);201// If the URL fragment does not change, the new path will not get pushed202// onto the stack. So we alternate the hash prefix to force a new state.203if (JX.History.getPath() === path) {204var hash = location.hash;205if (hash && hash.charAt(1) === '!') {206return '~!' + path;207}208}209return '!' + path;210},211212_parseFragment : function(fragment) {213if (fragment) {214if (fragment.charAt(1) === '!') {215return fragment.substr(2);216} else if (fragment.substr(1, 2) === '~!') {217return fragment.substr(3);218}219}220return null;221}222223}224225});226227228