288 lines
No EOL
14 KiB
JavaScript
288 lines
No EOL
14 KiB
JavaScript
define([], function() {
|
|
"use strict";
|
|
|
|
function page(path, fn) {
|
|
if ("function" == typeof path) return page("*", path);
|
|
if ("function" == typeof fn)
|
|
for (var route = new Route(path), i = 1; i < arguments.length; ++i) page.callbacks.push(route.middleware(arguments[i]));
|
|
else "string" == typeof path ? page["string" == typeof fn ? "redirect" : "show"](path, fn) : page.start(path)
|
|
}
|
|
|
|
function unhandled(ctx) {
|
|
if (!ctx.handled) {
|
|
var current;
|
|
current = hashbang ? base + location.hash.replace("#!", "") : location.pathname + location.search, current !== ctx.canonicalPath && (page.stop(), ctx.handled = !1, location.href = ctx.canonicalPath)
|
|
}
|
|
}
|
|
|
|
function decodeURLEncodedURIComponent(val) {
|
|
return "string" != typeof val ? val : decodeURLComponents ? decodeURIComponent(val.replace(/\+/g, " ")) : val
|
|
}
|
|
|
|
function Context(path, state) {
|
|
"/" === path[0] && 0 !== path.indexOf(base) && (path = base + (hashbang ? "#!" : "") + path);
|
|
var i = path.indexOf("?");
|
|
if (this.canonicalPath = path, this.path = path.replace(base, "") || "/", hashbang && (this.path = this.path.replace("#!", "") || "/"), this.title = document.title, this.state = state || {}, this.state.path = path, this.querystring = ~i ? decodeURLEncodedURIComponent(path.slice(i + 1)) : "", this.pathname = decodeURLEncodedURIComponent(~i ? path.slice(0, i) : path), this.params = {}, this.hash = "", !hashbang) {
|
|
if (!~this.path.indexOf("#")) return;
|
|
var parts = this.path.split("#");
|
|
this.path = parts[0], this.hash = decodeURLEncodedURIComponent(parts[1]) || "", this.querystring = this.querystring.split("#")[0]
|
|
}
|
|
}
|
|
|
|
function Route(path, options) {
|
|
options = options || {}, this.path = "*" === path ? "(.*)" : path, this.method = "GET", this.regexp = pathToRegexp(this.path, this.keys = [], options.sensitive, options.strict)
|
|
}
|
|
|
|
function ignorePopState(event) {
|
|
var state = event.state || {};
|
|
return !1 === previousPopState.navigate ? (previousPopState = state, !0) : (previousPopState = state, !1)
|
|
}
|
|
|
|
function onclick(e, checkWhich) {
|
|
if ((1 === which(e) || !1 === checkWhich) && !(e.metaKey || e.ctrlKey || e.shiftKey || e.defaultPrevented)) {
|
|
for (var el = e.target; el && "A" !== el.nodeName;) el = el.parentNode;
|
|
if (el && "A" === el.nodeName && !el.hasAttribute("download") && "external" !== el.getAttribute("rel")) {
|
|
var link = el.getAttribute("href");
|
|
if ("#" === link) return void e.preventDefault();
|
|
if ((hashbang || el.pathname !== location.pathname || !el.hash && "#" !== link) && !el.target && sameOrigin(el.href)) {
|
|
var path = el.pathname + el.search + (el.hash || ""),
|
|
orig = path;
|
|
0 === path.indexOf(base) && (path = path.substr(base.length)), hashbang && (path = path.replace("#!", "")), e.preventDefault(), page.show(orig)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function which(e) {
|
|
return e = e || window.event, null === e.which ? e.button : e.which
|
|
}
|
|
|
|
function sameOrigin(href) {
|
|
var origin = location.protocol + "//" + location.hostname;
|
|
return location.port && (origin += ":" + location.port), href && 0 === href.indexOf(origin)
|
|
}
|
|
|
|
function parse(str) {
|
|
for (var res, tokens = [], key = 0, index = 0, path = ""; null != (res = PATH_REGEXP.exec(str));) {
|
|
var m = res[0],
|
|
escaped = res[1],
|
|
offset = res.index;
|
|
if (path += str.slice(index, offset), index = offset + m.length, escaped) path += escaped[1];
|
|
else {
|
|
path && (tokens.push(path), path = "");
|
|
var prefix = res[2],
|
|
name = res[3],
|
|
capture = res[4],
|
|
group = res[5],
|
|
suffix = res[6],
|
|
asterisk = res[7],
|
|
repeat = "+" === suffix || "*" === suffix,
|
|
optional = "?" === suffix || "*" === suffix,
|
|
delimiter = prefix || "/",
|
|
pattern = capture || group || (asterisk ? ".*" : "[^" + delimiter + "]+?");
|
|
tokens.push({
|
|
name: name || key++,
|
|
prefix: prefix || "",
|
|
delimiter: delimiter,
|
|
optional: optional,
|
|
repeat: repeat,
|
|
pattern: escapeGroup(pattern)
|
|
})
|
|
}
|
|
}
|
|
return index < str.length && (path += str.substr(index)), path && tokens.push(path), tokens
|
|
}
|
|
|
|
function escapeString(str) {
|
|
return str.replace(/([.+*?=^!:${}()[\]|\/])/g, "\\$1")
|
|
}
|
|
|
|
function escapeGroup(group) {
|
|
return group.replace(/([=!:$\/()])/g, "\\$1")
|
|
}
|
|
|
|
function attachKeys(re, keys) {
|
|
return re.keys = keys, re
|
|
}
|
|
|
|
function flags(options) {
|
|
return options.sensitive ? "" : "i"
|
|
}
|
|
|
|
function regexpToRegexp(path, keys) {
|
|
var groups = path.source.match(/\((?!\?)/g);
|
|
if (groups)
|
|
for (var i = 0; i < groups.length; i++) keys.push({
|
|
name: i,
|
|
prefix: null,
|
|
delimiter: null,
|
|
optional: !1,
|
|
repeat: !1,
|
|
pattern: null
|
|
});
|
|
return attachKeys(path, keys)
|
|
}
|
|
|
|
function arrayToRegexp(path, keys, options) {
|
|
for (var parts = [], i = 0; i < path.length; i++) parts.push(pathToRegexp(path[i], keys, options).source);
|
|
return attachKeys(new RegExp("(?:" + parts.join("|") + ")", flags(options)), keys)
|
|
}
|
|
|
|
function stringToRegexp(path, keys, options) {
|
|
for (var tokens = parse(path), re = tokensToRegExp(tokens, options), i = 0; i < tokens.length; i++) "string" != typeof tokens[i] && keys.push(tokens[i]);
|
|
return attachKeys(re, keys)
|
|
}
|
|
|
|
function tokensToRegExp(tokens, options) {
|
|
options = options || {};
|
|
for (var strict = options.strict, end = !1 !== options.end, route = "", lastToken = tokens[tokens.length - 1], endsWithSlash = "string" == typeof lastToken && /\/$/.test(lastToken), i = 0; i < tokens.length; i++) {
|
|
var token = tokens[i];
|
|
if ("string" == typeof token) route += escapeString(token);
|
|
else {
|
|
var prefix = escapeString(token.prefix),
|
|
capture = token.pattern;
|
|
token.repeat && (capture += "(?:" + prefix + capture + ")*"), capture = token.optional ? prefix ? "(?:" + prefix + "(" + capture + "))?" : "(" + capture + ")?" : prefix + "(" + capture + ")", route += capture
|
|
}
|
|
}
|
|
return strict || (route = (endsWithSlash ? route.slice(0, -2) : route) + "(?:\\/(?=$))?"), route += end ? "$" : strict && endsWithSlash ? "" : "(?=\\/|$)", new RegExp("^" + route, flags(options))
|
|
}
|
|
|
|
function pathToRegexp(path, keys, options) {
|
|
return keys = keys || [], isarray(keys) ? options || (options = {}) : (options = keys, keys = []), path instanceof RegExp ? regexpToRegexp(path, keys, options) : isarray(path) ? arrayToRegexp(path, keys, options) : stringToRegexp(path, keys, options)
|
|
}
|
|
var running, prevContext, prevPageContext, clickEvent = "undefined" != typeof document && document.ontouchstart ? "touchstart" : "click",
|
|
location = "undefined" != typeof window && (window.history.location || window.location),
|
|
dispatch = !0,
|
|
decodeURLComponents = !0,
|
|
base = "",
|
|
hashbang = !1,
|
|
enableHistory = !1;
|
|
page.callbacks = [], page.exits = [], page.current = "", page.len = 0, page.base = function(path) {
|
|
if (0 === arguments.length) return base;
|
|
base = path
|
|
}, page.start = function(options) {
|
|
if (options = options || {}, !running && (running = !0, !1 === options.dispatch && (dispatch = !1), !1 === options.decodeURLComponents && (decodeURLComponents = !1), !1 !== options.popstate && window.addEventListener("popstate", onpopstate, !1), !1 !== options.click && document.addEventListener(clickEvent, onclick, !1), null != options.enableHistory && (enableHistory = options.enableHistory), !0 === options.hashbang && (hashbang = !0), dispatch)) {
|
|
var url;
|
|
if (hashbang && ~location.hash.indexOf("#!")) {
|
|
url = location.hash.substr(2);
|
|
var href = location.href.toString();
|
|
href.indexOf("?") >= href.indexOf("#!") && (url += location.search)
|
|
} else url = location.pathname + location.search + location.hash;
|
|
page.replace(url, null, !0, dispatch)
|
|
}
|
|
}, page.stop = function() {
|
|
running && (page.current = "", page.len = 0, running = !1, document.removeEventListener(clickEvent, onclick, !1), window.removeEventListener("popstate", onpopstate, !1))
|
|
}, page.show = function(path, state, dispatch, push, isBack) {
|
|
var ctx = new Context(path, state);
|
|
return ctx.isBack = isBack, page.current = ctx.path, !1 !== dispatch && page.dispatch(ctx), !1 !== ctx.handled && !1 !== push && ctx.pushState(), ctx
|
|
}, page.restorePreviousState = function() {
|
|
prevContext = prevPageContext, page.show(prevContext.pathname, prevContext.state, !1, !0, !1)
|
|
}, page.back = function(path, state) {
|
|
if (enableHistory) return void history.back();
|
|
if (page.len > 0) {
|
|
if (enableHistory) history.back();
|
|
else if (backStack.length > 2) {
|
|
backStack.length--;
|
|
var previousState = backStack[backStack.length - 1];
|
|
page.show(previousState.path, previousState.state, !0, !1, !0)
|
|
}
|
|
page.len--
|
|
} else path ? setTimeout(function() {
|
|
page.show(path, state)
|
|
}) : setTimeout(function() {
|
|
page.show(base, state)
|
|
})
|
|
}, page.enableNativeHistory = function() {
|
|
return enableHistory
|
|
}, page.canGoBack = function() {
|
|
return enableHistory ? history.length > 1 : (page.len || 0) > 0
|
|
}, page.redirect = function(from, to) {
|
|
"string" == typeof from && "string" == typeof to && page(from, function(e) {
|
|
setTimeout(function() {
|
|
page.replace(to)
|
|
}, 0)
|
|
}), "string" == typeof from && void 0 === to && setTimeout(function() {
|
|
page.replace(from)
|
|
}, 0)
|
|
}, page.replace = function(path, state, init, dispatch, isBack) {
|
|
var ctx = new Context(path, state);
|
|
return ctx.isBack = isBack, page.current = ctx.path, ctx.init = init, ctx.save(), !1 !== dispatch && page.dispatch(ctx), ctx
|
|
}, page.dispatch = function(ctx) {
|
|
function nextExit() {
|
|
var fn = page.exits[j++];
|
|
if (!fn) return nextEnter();
|
|
fn(prev, nextExit)
|
|
}
|
|
|
|
function nextEnter() {
|
|
var fn = page.callbacks[i++];
|
|
return ctx.path !== page.current ? void(ctx.handled = !1) : fn ? void fn(ctx, nextEnter) : unhandled(ctx)
|
|
}
|
|
var prev = prevContext,
|
|
i = 0,
|
|
j = 0;
|
|
prevPageContext = prevContext, prevContext = ctx, prev ? nextExit() : nextEnter()
|
|
}, page.exit = function(path, fn) {
|
|
if ("function" == typeof path) return page.exit("*", path);
|
|
for (var route = new Route(path), i = 1; i < arguments.length; ++i) page.exits.push(route.middleware(arguments[i]))
|
|
}, page.Context = Context;
|
|
var backStack = [];
|
|
Context.prototype.pushState = function() {
|
|
page.len++, enableHistory ? history.pushState(this.state, this.title, hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath) : backStack.push({
|
|
state: this.state,
|
|
title: this.title,
|
|
url: hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath,
|
|
path: this.path
|
|
})
|
|
}, Context.prototype.save = function() {
|
|
enableHistory ? history.replaceState(this.state, this.title, hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath) : backStack[page.len || 0] = {
|
|
state: this.state,
|
|
title: this.title,
|
|
url: hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath,
|
|
path: this.path
|
|
}
|
|
}, page.Route = Route, Route.prototype.middleware = function(fn) {
|
|
var self = this;
|
|
return function(ctx, next) {
|
|
if (self.match(ctx.path, ctx.params)) return fn(ctx, next);
|
|
next()
|
|
}
|
|
}, Route.prototype.match = function(path, params) {
|
|
var keys = this.keys,
|
|
qsIndex = path.indexOf("?"),
|
|
pathname = ~qsIndex ? path.slice(0, qsIndex) : path,
|
|
m = this.regexp.exec(decodeURIComponent(pathname));
|
|
if (!m) return !1;
|
|
for (var i = 1, len = m.length; i < len; ++i) {
|
|
var key = keys[i - 1],
|
|
val = decodeURLEncodedURIComponent(m[i]);
|
|
void 0 === val && hasOwnProperty.call(params, key.name) || (params[key.name] = val)
|
|
}
|
|
return !0
|
|
};
|
|
var previousPopState = {};
|
|
page.pushState = function(state, title, url) {
|
|
hashbang && (url = "#!" + url), history.pushState(state, title, url), previousPopState = state
|
|
};
|
|
var onpopstate = function() {
|
|
var loaded = !1;
|
|
if ("undefined" != typeof window) return "complete" === document.readyState ? loaded = !0 : window.addEventListener("load", function() {
|
|
setTimeout(function() {
|
|
loaded = !0
|
|
}, 0)
|
|
}),
|
|
function(e) {
|
|
if (loaded && !ignorePopState(e))
|
|
if (e.state) {
|
|
var path = e.state.path;
|
|
page.replace(path, e.state, null, null, !0)
|
|
} else page.show(location.pathname + location.hash, void 0, void 0, !1, !0)
|
|
}
|
|
}();
|
|
page.handleAnchorClick = onclick, page.sameOrigin = sameOrigin;
|
|
var PATH_REGEXP = new RegExp(["(\\\\.)", "([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))"].join("|"), "g"),
|
|
isarray = Array.isArray || function(arr) {
|
|
return "[object Array]" === Object.prototype.toString.call(arr)
|
|
};
|
|
return page
|
|
}); |