router.js | |
---|---|
Event-based URL handling:
When the URL (hash fragment) change the router sends a | define(
[
'jquery',
'./events'
],
function (jQuery, events) { |
Event bus/manager for routingManage all routing events. Usage:
| var handler = events.at('routing'); |
Default controllerUse controller.on(...) Example: path with parameters (starting with a '#')
Example: regexp path: | var controller = function () {
var that = {}; |
Private methods | |
Convert parameters (eg. '#userId') into regexp | function convertRoute(route) {
return route.replace(/#[\w\d]+/g, '([^\/]*)');
} |
Match route against path and return array of values for matched parameters in path. | function extractParameters(route, path) {
var parameterValues = new RegExp('^' + convertRoute(path) + '[\/]?$').exec(route);
if (parameterValues) {
return parameterValues.slice(1);
} else {
return null;
}
} |
Public API | |
Binds a callback to a path. Eg: | that.on = function (path, callback) {
if (typeof(path) === 'undefined' || path === null || typeof path !== "string") {
throw 'accepts only string paths';
}
handler.on(path, callback);
}; |
Tries to match registered bindings agains the new route from the router. | that.resolveRoute = function (route) {
var params,
numMatches = 0,
bindings = handler.events;
for (var path in bindings) {
if (bindings.hasOwnProperty(path)) {
params = extractParameters(route, path);
if (params) {
numMatches++;
handler.trigger.apply(this, [path].concat(params));
}
}
} |
Trigger 'notfound' event (with route as argument) if no match | if (numMatches === 0) {
handler.trigger('notfound', route);
}
};
return that;
}; |
| |
RouterListens for changes in the hash fragment of the URL. In modern browsers we use the 'hashchange' event. In legacy browsers we instead pull for changes using a timer/interval. In old IE (< IE 8) a hidden IFrame is created to allow the back button and hash-based history to work.
See Usage:
Router can be started before DOM is ready, but since it won’t be usable before then in IE6/7 (due to the necessary IFrame), recommended usage is to bind it inside a DOM ready handler. | var router = function (spec) {
spec = spec || {};
var that = {};
var fallback = true,
oldIE, // IE7 or older
fragment, // last hash fragment
history = [], // history of visited hash fragments
timer,
iframe_src = (spec.iframe_src || /*jshint scripturl:true*/ 'javascript:0'), /*jshint scripturl:false*/
controller; |
Initilize | |
Fallback only if no 'onhashchange' event | if ('onhashchange' in window) { fallback = false; } |
oldIE if IE < IE8 | oldIE = jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 8; |
Private methods | |
The route is simply the URL hash fragment minus the hash-bang (#!). Eg. bar in: http://foo.com/#!/bar | function route() {
return window.location.hash.replace(/^#![\/]?/, '');
} |
Get/Set the hash fragment | function getHash() {
return window.location.hash;
}
function setHash(string) {
window.location.hash = string;
} |
Setup IFrame for old versions of IE | function setupOldIE() {
var iDoc = jQuery("<iframe id='ie_history_iframe'" +
"src='" + iframe_src + "'" +
"style='display: none'></iframe>").prependTo("body")[0];
var iframe = iDoc.contentWindow.document || iDoc.document;
if (window.location.hash) {
var hash = window.location.hash.substr(1);
iframe.location.hash = hash;
}
iframe.location.title = window.title;
} |
Special hack for IE < 8 since hashchanges is not added to history. IE will add a history entry when IFrame is opened/closed. | function fixHistoryForIE() {
var iframe = getIframe();
iframe.open();
iframe.close();
iframe.location.hash = fragment;
}
function getIframe() {
return jQuery('#ie_history_iframe').get(0).contentWindow.document;
} |
Adds a fragment to history | function pushToHistory(aFragment) {
if (oldIE) {
fixHistoryForIE();
}
history.push(aFragment);
} |
Delegates route resolving to the current controller, if any | function resolveRoute() {
if (controller) {
controller.resolveRoute(route());
}
} |
Check if the url fragment has changed and resolve it if needed. | function check() {
var newFragment = getHash();
if (fragment !== newFragment /* route changed */) {
fragment = newFragment;
pushToHistory(fragment);
resolveRoute();
} else if (oldIE) {
var iframe = getIframe();
if (iframe.location.hash !== newFragment) {
setHash(iframe.location.hash);
pushToHistory(fragment);
resolveRoute();
}
}
} |
Public API | |
Return current route | that.route = route;
that.linkTo = function (path) {
if (typeof(path) === 'undefined' || path === null || typeof path !== "string") {
throw 'accepts only string paths';
}
return ('#!/' + path);
};
that.redirectTo = function (path) {
setHash(that.linkTo(path));
}; |
Navigate to previous fragment. Fallback to the `fallback' url if the history is empty | that.back = function (fallback) {
if (history.length > 1) {
history.pop();
setHash(history.pop());
} else if (fallback) {
setHash(that.linkTo(fallback));
}
};
|
Force a check(), whether the fragment has changed or not. | that.forceCheck = resolveRoute; |
Register a controller | that.register = function (aController) { |
Only one controller can be registered at a time | controller = aController; |
Also send controller route() if started | if(fragment !== undefined) {
resolveRoute();
}
}; |
Start/stop url changes check. If the browser supports it we bind 'check()' to the 'hashchange' event. In legacy browsers we instead pull for changes every 100 ms. | that.start = function () {
that.stop();
fragment = getHash();
history = [fragment];
if (fallback) {
if (oldIE) {
setupOldIE();
}
timer = setInterval(check, 100);
} else {
jQuery(window).bind('hashchange', check);
}
resolveRoute(); //send controller our route()
};
that.stop = function () {
if (timer) {
timer.clearInterval();
timer = null;
}
jQuery(window).unbind('hashchange', check);
jQuery('#ie_history_iframe').remove(); // remove any IFRAME
};
return that;
}; |
ExportsInstances of default router and controller | var current_router = router();
var current_controller = controller();
current_router.register(current_controller);
return {
controller: current_controller,
router: current_router
};
});
|