todomvc
Version:
> Helping you select an MV\* framework
387 lines (312 loc) • 10.8 kB
JavaScript
/*
* Kendo UI Web v2013.3.1119 (http://kendoui.com)
* Copyright 2013 Telerik AD. All rights reserved.
*
* Kendo UI Web commercial licenses may be obtained at
* https://www.kendoui.com/purchase/license-agreement/kendo-ui-web-commercial.aspx
* If you do not own a commercial license, this file shall be governed by the
* GNU General Public License (GPL) version 3.
* For GPL requirements, please review: http://www.gnu.org/copyleft/gpl.html
*/
kendo_module({
id: "router",
name: "Router",
category: "framework",
description: "The Router class is responsible for tracking the application state and navigating between the application states.",
depends: [ "core" ],
hidden: false
});
(function($, undefined) {
var kendo = window.kendo,
CHANGE = "change",
BACK = "back",
support = kendo.support,
location = window.location,
history = window.history,
CHECK_URL_INTERVAL = 50,
hashStrip = /^#*/,
document = window.document;
function absoluteURL(path, pathPrefix) {
if (!pathPrefix) {
return path;
}
if (path + "/" === pathPrefix) {
path = pathPrefix;
}
var regEx = new RegExp("^" + pathPrefix, "i");
if (!regEx.test(path)) {
path = pathPrefix + "/" + path;
}
return location.protocol + '//' + (location.host + "/" + path).replace(/\/\/+/g, '/');
}
function stripRoot(root, url) {
if (url.indexOf(root) === 0) {
return (url.substr(root.length)).replace(/\/\//g, '/');
} else {
return root;
}
}
var PushStateAdapter = kendo.Class.extend({
init: function(root) {
this.root = root;
},
navigate: function(to) {
history.pushState({}, document.title, absoluteURL(to, this.root));
return this.current();
},
current: function() {
var current = location.pathname;
if (location.search) {
current += location.search;
}
return stripRoot(this.root, current);
},
change: function(callback) {
$(window).bind("popstate.kendo", callback);
},
stop: function() {
$(window).unbind("popstate.kendo");
}
});
var HashAdapter = kendo.Class.extend({
navigate: function(to) {
location.hash = to;
return to;
},
change: function(callback) {
if (support.hashChange) {
$(window).bind("hashchange.kendo", callback);
} else {
this._interval = setInterval(callback, CHECK_URL_INTERVAL);
}
},
stop: function() {
$(window).unbind("popstate.kendo");
clearInterval(this._interval);
},
current: function() {
return location.hash.replace(hashStrip, '');
}
});
var History = kendo.Observable.extend({
start: function(options) {
options = options || {};
this.bind([CHANGE, BACK], options);
if (this._started) {
return;
}
this._started = true;
var pathname = location.pathname,
hash = location.hash,
pushState = support.pushState && options.pushState,
root = options.root || "/",
atRoot = root === pathname;
this.adapter = pushState ? new PushStateAdapter(root) : new HashAdapter();
if (options.pushState && !support.pushState && !atRoot) {
location.replace(root + '#' + stripRoot(root, pathname));
return true; // browser will reload at this point.
}
if (pushState) {
var fixedUrl;
if (root === pathname + "/") {
fixedUrl = root;
}
if (atRoot && hash) {
fixedUrl = absoluteURL(hash.replace(hashStrip, ''), root);
}
if (fixedUrl) {
history.replaceState({}, document.title, fixedUrl);
}
}
this.root = root;
this.current = this.adapter.current();
this.locations = [this.current];
this.adapter.change($.proxy(this, "_checkUrl"));
},
stop: function() {
if (!this._started) {
return;
}
this.adapter.stop();
this.unbind(CHANGE);
this._started = false;
},
change: function(callback) {
this.bind(CHANGE, callback);
},
navigate: function(to, silent) {
if (to === "#:back") {
history.back();
return;
}
to = to.replace(hashStrip, '');
if (this.current === to || this.current === decodeURIComponent(to)) {
return;
}
if (!silent) {
if (this.trigger(CHANGE, { url: to })) {
return;
}
}
this.current = this.adapter.navigate(to);
this.locations.push(this.current);
},
_checkUrl: function() {
var current = this.adapter.current(),
back = current === this.locations[this.locations.length - 2],
prev = this.current;
if (this.current === current || this.current === decodeURIComponent(current)) {
return;
}
this.current = current;
if (back && this.trigger("back", { url: prev, to: current })) {
history.forward();
this.current = prev;
return;
}
if (this.trigger(CHANGE, { url: current })) {
if (back) {
history.forward();
} else {
history.back();
}
this.current = prev;
return;
}
if (back) {
this.locations.pop();
} else {
this.locations.push(current);
}
}
});
kendo.absoluteURL = absoluteURL;
kendo.history = new History();
})(window.kendo.jQuery);
(function() {
var kendo = window.kendo,
history = kendo.history,
Observable = kendo.Observable,
INIT = "init",
ROUTE_MISSING = "routeMissing",
CHANGE = "change",
BACK = "back",
optionalParam = /\((.*?)\)/g,
namedParam = /(\(\?)?:\w+/g,
splatParam = /\*\w+/g,
escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
function namedParamReplace(match, optional) {
return optional ? match : '([^\/]+)';
}
function routeToRegExp(route) {
return new RegExp('^' + route
.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, namedParamReplace)
.replace(splatParam, '(.*?)') + '$');
}
function stripUrl(url) {
return url.replace(/(\?.*)|(#.*)/g, "");
}
var Route = kendo.Class.extend({
init: function(route, callback) {
if (!(route instanceof RegExp)) {
route = routeToRegExp(route);
}
this.route = route;
this._callback = callback;
},
callback: function(url) {
var params,
idx = 0,
length,
queryStringParams = kendo.parseQueryStringParams(url);
url = stripUrl(url);
params = this.route.exec(url).slice(1);
length = params.length;
for (; idx < length; idx ++) {
if (typeof params[idx] !== 'undefined') {
params[idx] = decodeURIComponent(params[idx]);
}
}
params.push(queryStringParams);
this._callback.apply(null, params);
},
worksWith: function(url) {
if (this.route.test(url)) {
this.callback(url);
return true;
} else {
return false;
}
}
});
var Router = Observable.extend({
init: function(options) {
Observable.fn.init.call(this);
this.routes = [];
this.pushState = options ? options.pushState : false;
if (options && options.root) {
this.root = options.root;
}
this.bind([INIT, ROUTE_MISSING, CHANGE], options);
},
destroy: function() {
history.unbind(CHANGE, this._urlChangedProxy);
history.unbind(BACK, this._backProxy);
this.unbind();
},
start: function() {
var that = this,
backProxy = function(e) { that._back(e); },
urlChangedProxy = function(e) { that._urlChanged(e); };
history.start({
change: urlChangedProxy,
back: backProxy,
pushState: that.pushState,
root: that.root
});
var initEventObject = { url: history.current || "/" };
if (!that.trigger(INIT, initEventObject)) {
that._urlChanged(initEventObject);
}
this._urlChangedProxy = urlChangedProxy;
this._backProxy = backProxy;
},
route: function(route, callback) {
this.routes.push(new Route(route, callback));
},
navigate: function(url, silent) {
kendo.history.navigate(url, silent);
},
_back: function(e) {
if (this.trigger(BACK, { url: e.url, to: e.to })) {
e.preventDefault();
}
},
_urlChanged: function(e) {
var url = e.url;
if (!url) {
url = "/";
}
if (this.trigger(CHANGE, { url: e.url, params: kendo.parseQueryStringParams(e.url) })) {
e.preventDefault();
return;
}
var idx = 0,
routes = this.routes,
route,
length = routes.length;
for (; idx < length; idx ++) {
route = routes[idx];
if (route.worksWith(url)) {
return;
}
}
if (this.trigger(ROUTE_MISSING, { url: url, params: kendo.parseQueryStringParams(url) })) {
e.preventDefault();
}
}
});
kendo.Router = Router;
})();