mirror of
https://github.com/Febbweiss/febbweiss.github.io.git
synced 2026-03-05 06:35:50 +00:00
333 lines
12 KiB
JavaScript
333 lines
12 KiB
JavaScript
/**
|
|
* Durandal 2.1.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved.
|
|
* Available via the MIT license.
|
|
* see: http://durandaljs.com or https://github.com/BlueSpire/Durandal for details.
|
|
*/
|
|
/**
|
|
* This module is based on Backbone's core history support. It abstracts away the low level details of working with browser history and url changes in order to provide a solid foundation for a router.
|
|
* @module history
|
|
* @requires system
|
|
* @requires jquery
|
|
*/
|
|
define(['durandal/system', 'jquery'], function (system, $) {
|
|
// Cached regex for stripping a leading hash/slash and trailing space.
|
|
var routeStripper = /^[#\/]|\s+$/g;
|
|
|
|
// Cached regex for stripping leading and trailing slashes.
|
|
var rootStripper = /^\/+|\/+$/g;
|
|
|
|
// Cached regex for detecting MSIE.
|
|
var isExplorer = /msie [\w.]+/;
|
|
|
|
// Cached regex for removing a trailing slash.
|
|
var trailingSlash = /\/$/;
|
|
|
|
// Update the hash location, either replacing the current entry, or adding
|
|
// a new one to the browser history.
|
|
function updateHash(location, fragment, replace) {
|
|
if (replace) {
|
|
var href = location.href.replace(/(javascript:|#).*$/, '');
|
|
|
|
if (history.history.replaceState) {
|
|
history.history.replaceState({}, document.title, href + '#' + fragment); // using history.replaceState instead of location.replace to work around chrom bug
|
|
} else {
|
|
location.replace(href + '#' + fragment);
|
|
}
|
|
} else {
|
|
// Some browsers require that `hash` contains a leading #.
|
|
location.hash = '#' + fragment;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @class HistoryModule
|
|
* @static
|
|
*/
|
|
var history = {
|
|
/**
|
|
* The setTimeout interval used when the browser does not support hash change events.
|
|
* @property {string} interval
|
|
* @default 50
|
|
*/
|
|
interval: 50,
|
|
/**
|
|
* Indicates whether or not the history module is actively tracking history.
|
|
* @property {string} active
|
|
*/
|
|
active: false
|
|
};
|
|
|
|
// Ensure that `History` can be used outside of the browser.
|
|
if (typeof window !== 'undefined') {
|
|
history.location = window.location;
|
|
history.history = window.history;
|
|
}
|
|
|
|
/**
|
|
* Gets the true hash value. Cannot use location.hash directly due to a bug in Firefox where location.hash will always be decoded.
|
|
* @method getHash
|
|
* @param {string} [window] The optional window instance
|
|
* @return {string} The hash.
|
|
*/
|
|
history.getHash = function(window) {
|
|
var match = (window || history).location.href.match(/#(.*)$/);
|
|
return match ? match[1] : '';
|
|
};
|
|
|
|
/**
|
|
* Get the cross-browser normalized URL fragment, either from the URL, the hash, or the override.
|
|
* @method getFragment
|
|
* @param {string} fragment The fragment.
|
|
* @param {boolean} forcePushState Should we force push state?
|
|
* @return {string} he fragment.
|
|
*/
|
|
history.getFragment = function(fragment, forcePushState) {
|
|
if (fragment == null) {
|
|
if (history._hasPushState || !history._wantsHashChange || forcePushState) {
|
|
fragment = history.location.pathname + history.location.search;
|
|
var root = history.root.replace(trailingSlash, '');
|
|
if (!fragment.indexOf(root)) {
|
|
fragment = fragment.substr(root.length);
|
|
}
|
|
} else {
|
|
fragment = history.getHash();
|
|
}
|
|
}
|
|
|
|
return fragment.replace(routeStripper, '');
|
|
};
|
|
|
|
/**
|
|
* Activate the hash change handling, returning `true` if the current URL matches an existing route, and `false` otherwise.
|
|
* @method activate
|
|
* @param {HistoryOptions} options.
|
|
* @return {boolean|undefined} Returns true/false from loading the url unless the silent option was selected.
|
|
*/
|
|
history.activate = function(options) {
|
|
if (history.active) {
|
|
system.error("History has already been activated.");
|
|
}
|
|
|
|
history.active = true;
|
|
|
|
// Figure out the initial configuration. Do we need an iframe?
|
|
// Is pushState desired ... is it available?
|
|
history.options = system.extend({}, { root: '/' }, history.options, options);
|
|
history.root = history.options.root;
|
|
history._wantsHashChange = history.options.hashChange !== false;
|
|
history._wantsPushState = !!history.options.pushState;
|
|
history._hasPushState = !!(history.options.pushState && history.history && history.history.pushState);
|
|
|
|
var fragment = history.getFragment();
|
|
var docMode = document.documentMode;
|
|
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
|
|
|
|
// Normalize root to always include a leading and trailing slash.
|
|
history.root = ('/' + history.root + '/').replace(rootStripper, '/');
|
|
|
|
if (oldIE && history._wantsHashChange) {
|
|
history.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
|
|
history.navigate(fragment, false);
|
|
}
|
|
|
|
// Depending on whether we're using pushState or hashes, and whether
|
|
// 'onhashchange' is supported, determine how we check the URL state.
|
|
if (history._hasPushState) {
|
|
$(window).on('popstate', history.checkUrl);
|
|
} else if (history._wantsHashChange && ('onhashchange' in window) && !oldIE) {
|
|
$(window).on('hashchange', history.checkUrl);
|
|
} else if (history._wantsHashChange) {
|
|
history._checkUrlInterval = setInterval(history.checkUrl, history.interval);
|
|
}
|
|
|
|
// Determine if we need to change the base url, for a pushState link
|
|
// opened by a non-pushState browser.
|
|
history.fragment = fragment;
|
|
var loc = history.location;
|
|
var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === history.root;
|
|
|
|
// Transition from hashChange to pushState or vice versa if both are requested.
|
|
if (history._wantsHashChange && history._wantsPushState) {
|
|
// If we've started off with a route from a `pushState`-enabled
|
|
// browser, but we're currently in a browser that doesn't support it...
|
|
if (!history._hasPushState && !atRoot) {
|
|
history.fragment = history.getFragment(null, true);
|
|
history.location.replace(history.root + history.location.search + '#' + history.fragment);
|
|
// Return immediately as browser will do redirect to new url
|
|
return true;
|
|
|
|
// Or if we've started out with a hash-based route, but we're currently
|
|
// in a browser where it could be `pushState`-based instead...
|
|
} else if (history._hasPushState && atRoot && loc.hash) {
|
|
this.fragment = history.getHash().replace(routeStripper, '');
|
|
this.history.replaceState({}, document.title, history.root + history.fragment + loc.search);
|
|
}
|
|
}
|
|
|
|
if (!history.options.silent) {
|
|
return history.loadUrl(options.startRoute);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Disable history, perhaps temporarily. Not useful in a real app, but possibly useful for unit testing Routers.
|
|
* @method deactivate
|
|
*/
|
|
history.deactivate = function() {
|
|
$(window).off('popstate', history.checkUrl).off('hashchange', history.checkUrl);
|
|
clearInterval(history._checkUrlInterval);
|
|
history.active = false;
|
|
};
|
|
|
|
/**
|
|
* Checks the current URL to see if it has changed, and if it has, calls `loadUrl`, normalizing across the hidden iframe.
|
|
* @method checkUrl
|
|
* @return {boolean} Returns true/false from loading the url.
|
|
*/
|
|
history.checkUrl = function() {
|
|
var current = history.getFragment();
|
|
if (current === history.fragment && history.iframe) {
|
|
current = history.getFragment(history.getHash(history.iframe));
|
|
}
|
|
|
|
if (current === history.fragment) {
|
|
return false;
|
|
}
|
|
|
|
if (history.iframe) {
|
|
history.navigate(current, false);
|
|
}
|
|
|
|
history.loadUrl();
|
|
};
|
|
|
|
/**
|
|
* Attempts to load the current URL fragment. A pass-through to options.routeHandler.
|
|
* @method loadUrl
|
|
* @return {boolean} Returns true/false from the route handler.
|
|
*/
|
|
history.loadUrl = function(fragmentOverride) {
|
|
var fragment = history.fragment = history.getFragment(fragmentOverride);
|
|
|
|
return history.options.routeHandler ?
|
|
history.options.routeHandler(fragment) :
|
|
false;
|
|
};
|
|
|
|
/**
|
|
* Save a fragment into the hash history, or replace the URL state if the
|
|
* 'replace' option is passed. You are responsible for properly URL-encoding
|
|
* the fragment in advance.
|
|
* The options object can contain `trigger: false` if you wish to not have the
|
|
* route callback be fired, or `replace: true`, if
|
|
* you wish to modify the current URL without adding an entry to the history.
|
|
* @method navigate
|
|
* @param {string} fragment The url fragment to navigate to.
|
|
* @param {object|boolean} options An options object with optional trigger and replace flags. You can also pass a boolean directly to set the trigger option. Trigger is `true` by default.
|
|
* @return {boolean} Returns true/false from loading the url.
|
|
*/
|
|
history.navigate = function(fragment, options) {
|
|
if (!history.active) {
|
|
return false;
|
|
}
|
|
|
|
if(options === undefined) {
|
|
options = {
|
|
trigger: true
|
|
};
|
|
}else if(system.isBoolean(options)) {
|
|
options = {
|
|
trigger: options
|
|
};
|
|
}
|
|
|
|
fragment = history.getFragment(fragment || '');
|
|
|
|
if (history.fragment === fragment) {
|
|
return;
|
|
}
|
|
|
|
history.fragment = fragment;
|
|
|
|
var url = history.root + fragment;
|
|
|
|
// Don't include a trailing slash on the root.
|
|
if(fragment === '' && url !== '/') {
|
|
url = url.slice(0, -1);
|
|
}
|
|
|
|
// If pushState is available, we use it to set the fragment as a real URL.
|
|
if (history._hasPushState) {
|
|
history.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
|
|
|
|
// If hash changes haven't been explicitly disabled, update the hash
|
|
// fragment to store history.
|
|
} else if (history._wantsHashChange) {
|
|
updateHash(history.location, fragment, options.replace);
|
|
|
|
if (history.iframe && (fragment !== history.getFragment(history.getHash(history.iframe)))) {
|
|
// Opening and closing the iframe tricks IE7 and earlier to push a
|
|
// history entry on hash-tag change. When replace is true, we don't
|
|
// want history.
|
|
if (!options.replace) {
|
|
history.iframe.document.open().close();
|
|
}
|
|
|
|
updateHash(history.iframe.location, fragment, options.replace);
|
|
}
|
|
|
|
// If you've told us that you explicitly don't want fallback hashchange-
|
|
// based history, then `navigate` becomes a page refresh.
|
|
} else {
|
|
return history.location.assign(url);
|
|
}
|
|
|
|
if (options.trigger) {
|
|
return history.loadUrl(fragment);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Navigates back in the browser history.
|
|
* @method navigateBack
|
|
*/
|
|
history.navigateBack = function() {
|
|
history.history.back();
|
|
};
|
|
|
|
/**
|
|
* @class HistoryOptions
|
|
* @static
|
|
*/
|
|
|
|
/**
|
|
* The function that will be called back when the fragment changes.
|
|
* @property {function} routeHandler
|
|
*/
|
|
|
|
/**
|
|
* The url root used to extract the fragment when using push state.
|
|
* @property {string} root
|
|
*/
|
|
|
|
/**
|
|
* Use hash change when present.
|
|
* @property {boolean} hashChange
|
|
* @default true
|
|
*/
|
|
|
|
/**
|
|
* Use push state when present.
|
|
* @property {boolean} pushState
|
|
* @default false
|
|
*/
|
|
|
|
/**
|
|
* Prevents loading of the current url when activating history.
|
|
* @property {boolean} silent
|
|
* @default false
|
|
*/
|
|
|
|
return history;
|
|
});
|