This commit is contained in:
2015-04-09 07:47:57 +00:00
commit 32da2df9be
809 changed files with 119606 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
/**
* 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;
});