| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725 |
- //
- // Generated on Tue Dec 16 2014 12:13:47 GMT+0100 (CET) by Charlie Robbins, Paolo Fragomeni & the Contributors (Using Codesurgeon).
- // Version 1.2.6
- //
- (function (exports) {
- /*
- * browser.js: Browser specific functionality for director.
- *
- * (C) 2011, Charlie Robbins, Paolo Fragomeni, & the Contributors.
- * MIT LICENSE
- *
- */
- var dloc = document.location;
- function dlocHashEmpty() {
- // Non-IE browsers return '' when the address bar shows '#'; Director's logic
- // assumes both mean empty.
- return dloc.hash === '' || dloc.hash === '#';
- }
- var listener = {
- mode: 'modern',
- hash: dloc.hash,
- history: false,
- check: function () {
- var h = dloc.hash;
- if (h != this.hash) {
- this.hash = h;
- this.onHashChanged();
- }
- },
- fire: function () {
- if (this.mode === 'modern') {
- this.history === true ? window.onpopstate() : window.onhashchange();
- }
- else {
- this.onHashChanged();
- }
- },
- init: function (fn, history) {
- var self = this;
- this.history = history;
- if (!Router.listeners) {
- Router.listeners = [];
- }
- function onchange(onChangeEvent) {
- for (var i = 0, l = Router.listeners.length; i < l; i++) {
- Router.listeners[i](onChangeEvent);
- }
- }
- //note IE8 is being counted as 'modern' because it has the hashchange event
- if ('onhashchange' in window && (document.documentMode === undefined
- || document.documentMode > 7)) {
- // At least for now HTML5 history is available for 'modern' browsers only
- if (this.history === true) {
- // There is an old bug in Chrome that causes onpopstate to fire even
- // upon initial page load. Since the handler is run manually in init(),
- // this would cause Chrome to run it twise. Currently the only
- // workaround seems to be to set the handler after the initial page load
- // http://code.google.com/p/chromium/issues/detail?id=63040
- setTimeout(function() {
- window.onpopstate = onchange;
- }, 500);
- }
- else {
- window.onhashchange = onchange;
- }
- this.mode = 'modern';
- }
- else {
- //
- // IE support, based on a concept by Erik Arvidson ...
- //
- var frame = document.createElement('iframe');
- frame.id = 'state-frame';
- frame.style.display = 'none';
- document.body.appendChild(frame);
- this.writeFrame('');
- if ('onpropertychange' in document && 'attachEvent' in document) {
- document.attachEvent('onpropertychange', function () {
- if (event.propertyName === 'location') {
- self.check();
- }
- });
- }
- window.setInterval(function () { self.check(); }, 50);
- this.onHashChanged = onchange;
- this.mode = 'legacy';
- }
- Router.listeners.push(fn);
- return this.mode;
- },
- destroy: function (fn) {
- if (!Router || !Router.listeners) {
- return;
- }
- var listeners = Router.listeners;
- for (var i = listeners.length - 1; i >= 0; i--) {
- if (listeners[i] === fn) {
- listeners.splice(i, 1);
- }
- }
- },
- setHash: function (s) {
- // Mozilla always adds an entry to the history
- if (this.mode === 'legacy') {
- this.writeFrame(s);
- }
- if (this.history === true) {
- window.history.pushState({}, document.title, s);
- // Fire an onpopstate event manually since pushing does not obviously
- // trigger the pop event.
- this.fire();
- } else {
- dloc.hash = (s[0] === '/') ? s : '/' + s;
- }
- return this;
- },
- writeFrame: function (s) {
- // IE support...
- var f = document.getElementById('state-frame');
- var d = f.contentDocument || f.contentWindow.document;
- d.open();
- d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
- d.close();
- },
- syncHash: function () {
- // IE support...
- var s = this._hash;
- if (s != dloc.hash) {
- dloc.hash = s;
- }
- return this;
- },
- onHashChanged: function () {}
- };
- var Router = exports.Router = function (routes) {
- if (!(this instanceof Router)) return new Router(routes);
- this.params = {};
- this.routes = {};
- this.methods = ['on', 'once', 'after', 'before'];
- this.scope = [];
- this._methods = {};
- this._insert = this.insert;
- this.insert = this.insertEx;
- this.historySupport = (window.history != null ? window.history.pushState : null) != null
- this.configure();
- this.mount(routes || {});
- };
- Router.prototype.init = function (r) {
- var self = this
- , routeTo;
- this.handler = function(onChangeEvent) {
- var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash;
- var url = self.history === true ? self.getPath() : newURL.replace(/.*#/, '');
- self.dispatch('on', url.charAt(0) === '/' ? url : '/' + url);
- };
- listener.init(this.handler, this.history);
- if (this.history === false) {
- if (dlocHashEmpty() && r) {
- dloc.hash = r;
- } else if (!dlocHashEmpty()) {
- self.dispatch('on', '/' + dloc.hash.replace(/^(#\/|#|\/)/, ''));
- }
- }
- else {
- if (this.convert_hash_in_init) {
- // Use hash as route
- routeTo = dlocHashEmpty() && r ? r : !dlocHashEmpty() ? dloc.hash.replace(/^#/, '') : null;
- if (routeTo) {
- window.history.replaceState({}, document.title, routeTo);
- }
- }
- else {
- // Use canonical url
- routeTo = this.getPath();
- }
- // Router has been initialized, but due to the chrome bug it will not
- // yet actually route HTML5 history state changes. Thus, decide if should route.
- if (routeTo || this.run_in_init === true) {
- this.handler();
- }
- }
- return this;
- };
- Router.prototype.explode = function () {
- var v = this.history === true ? this.getPath() : dloc.hash;
- if (v.charAt(1) === '/') { v=v.slice(1) }
- return v.slice(1, v.length).split("/");
- };
- Router.prototype.setRoute = function (i, v, val) {
- var url = this.explode();
- if (typeof i === 'number' && typeof v === 'string') {
- url[i] = v;
- }
- else if (typeof val === 'string') {
- url.splice(i, v, s);
- }
- else {
- url = [i];
- }
- listener.setHash(url.join('/'));
- return url;
- };
- //
- // ### function insertEx(method, path, route, parent)
- // #### @method {string} Method to insert the specific `route`.
- // #### @path {Array} Parsed path to insert the `route` at.
- // #### @route {Array|function} Route handlers to insert.
- // #### @parent {Object} **Optional** Parent "routes" to insert into.
- // insert a callback that will only occur once per the matched route.
- //
- Router.prototype.insertEx = function(method, path, route, parent) {
- if (method === "once") {
- method = "on";
- route = function(route) {
- var once = false;
- return function() {
- if (once) return;
- once = true;
- return route.apply(this, arguments);
- };
- }(route);
- }
- return this._insert(method, path, route, parent);
- };
- Router.prototype.getRoute = function (v) {
- var ret = v;
- if (typeof v === "number") {
- ret = this.explode()[v];
- }
- else if (typeof v === "string"){
- var h = this.explode();
- ret = h.indexOf(v);
- }
- else {
- ret = this.explode();
- }
- return ret;
- };
- Router.prototype.destroy = function () {
- listener.destroy(this.handler);
- return this;
- };
- Router.prototype.getPath = function () {
- var path = window.location.pathname;
- if (path.substr(0, 1) !== '/') {
- path = '/' + path;
- }
- return path;
- };
- function _every(arr, iterator) {
- for (var i = 0; i < arr.length; i += 1) {
- if (iterator(arr[i], i, arr) === false) {
- return;
- }
- }
- }
- function _flatten(arr) {
- var flat = [];
- for (var i = 0, n = arr.length; i < n; i++) {
- flat = flat.concat(arr[i]);
- }
- return flat;
- }
- function _asyncEverySeries(arr, iterator, callback) {
- if (!arr.length) {
- return callback();
- }
- var completed = 0;
- (function iterate() {
- iterator(arr[completed], function(err) {
- if (err || err === false) {
- callback(err);
- callback = function() {};
- } else {
- completed += 1;
- if (completed === arr.length) {
- callback();
- } else {
- iterate();
- }
- }
- });
- })();
- }
- function paramifyString(str, params, mod) {
- mod = str;
- for (var param in params) {
- if (params.hasOwnProperty(param)) {
- mod = params[param](str);
- if (mod !== str) {
- break;
- }
- }
- }
- return mod === str ? "([._a-zA-Z0-9-%()]+)" : mod;
- }
- function regifyString(str, params) {
- var matches, last = 0, out = "";
- while (matches = str.substr(last).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/)) {
- last = matches.index + matches[0].length;
- matches[0] = matches[0].replace(/^\*/, "([_.()!\\ %@&a-zA-Z0-9-]+)");
- out += str.substr(0, matches.index) + matches[0];
- }
- str = out += str.substr(last);
- var captures = str.match(/:([^\/]+)/ig), capture, length;
- if (captures) {
- length = captures.length;
- for (var i = 0; i < length; i++) {
- capture = captures[i];
- if (capture.slice(0, 2) === "::") {
- str = capture.slice(1);
- } else {
- str = str.replace(capture, paramifyString(capture, params));
- }
- }
- }
- return str;
- }
- function terminator(routes, delimiter, start, stop) {
- var last = 0, left = 0, right = 0, start = (start || "(").toString(), stop = (stop || ")").toString(), i;
- for (i = 0; i < routes.length; i++) {
- var chunk = routes[i];
- if (chunk.indexOf(start, last) > chunk.indexOf(stop, last) || ~chunk.indexOf(start, last) && !~chunk.indexOf(stop, last) || !~chunk.indexOf(start, last) && ~chunk.indexOf(stop, last)) {
- left = chunk.indexOf(start, last);
- right = chunk.indexOf(stop, last);
- if (~left && !~right || !~left && ~right) {
- var tmp = routes.slice(0, (i || 1) + 1).join(delimiter);
- routes = [ tmp ].concat(routes.slice((i || 1) + 1));
- }
- last = (right > left ? right : left) + 1;
- i = 0;
- } else {
- last = 0;
- }
- }
- return routes;
- }
- var QUERY_SEPARATOR = /\?.*/;
- Router.prototype.configure = function(options) {
- options = options || {};
- for (var i = 0; i < this.methods.length; i++) {
- this._methods[this.methods[i]] = true;
- }
- this.recurse = options.recurse || this.recurse || false;
- this.async = options.async || false;
- this.delimiter = options.delimiter || "/";
- this.strict = typeof options.strict === "undefined" ? true : options.strict;
- this.notfound = options.notfound;
- this.resource = options.resource;
- this.history = options.html5history && this.historySupport || false;
- this.run_in_init = this.history === true && options.run_handler_in_init !== false;
- this.convert_hash_in_init = this.history === true && options.convert_hash_in_init !== false;
- this.every = {
- after: options.after || null,
- before: options.before || null,
- on: options.on || null
- };
- return this;
- };
- Router.prototype.param = function(token, matcher) {
- if (token[0] !== ":") {
- token = ":" + token;
- }
- var compiled = new RegExp(token, "g");
- this.params[token] = function(str) {
- return str.replace(compiled, matcher.source || matcher);
- };
- return this;
- };
- Router.prototype.on = Router.prototype.route = function(method, path, route) {
- var self = this;
- if (!route && typeof path == "function") {
- route = path;
- path = method;
- method = "on";
- }
- if (Array.isArray(path)) {
- return path.forEach(function(p) {
- self.on(method, p, route);
- });
- }
- if (path.source) {
- path = path.source.replace(/\\\//ig, "/");
- }
- if (Array.isArray(method)) {
- return method.forEach(function(m) {
- self.on(m.toLowerCase(), path, route);
- });
- }
- path = path.split(new RegExp(this.delimiter));
- path = terminator(path, this.delimiter);
- this.insert(method, this.scope.concat(path), route);
- };
- Router.prototype.path = function(path, routesFn) {
- var self = this, length = this.scope.length;
- if (path.source) {
- path = path.source.replace(/\\\//ig, "/");
- }
- path = path.split(new RegExp(this.delimiter));
- path = terminator(path, this.delimiter);
- this.scope = this.scope.concat(path);
- routesFn.call(this, this);
- this.scope.splice(length, path.length);
- };
- Router.prototype.dispatch = function(method, path, callback) {
- var self = this, fns = this.traverse(method, path.replace(QUERY_SEPARATOR, ""), this.routes, ""), invoked = this._invoked, after;
- this._invoked = true;
- if (!fns || fns.length === 0) {
- this.last = [];
- if (typeof this.notfound === "function") {
- this.invoke([ this.notfound ], {
- method: method,
- path: path
- }, callback);
- }
- return false;
- }
- if (this.recurse === "forward") {
- fns = fns.reverse();
- }
- function updateAndInvoke() {
- self.last = fns.after;
- self.invoke(self.runlist(fns), self, callback);
- }
- after = this.every && this.every.after ? [ this.every.after ].concat(this.last) : [ this.last ];
- if (after && after.length > 0 && invoked) {
- if (this.async) {
- this.invoke(after, this, updateAndInvoke);
- } else {
- this.invoke(after, this);
- updateAndInvoke();
- }
- return true;
- }
- updateAndInvoke();
- return true;
- };
- Router.prototype.invoke = function(fns, thisArg, callback) {
- var self = this;
- var apply;
- if (this.async) {
- apply = function(fn, next) {
- if (Array.isArray(fn)) {
- return _asyncEverySeries(fn, apply, next);
- } else if (typeof fn == "function") {
- fn.apply(thisArg, (fns.captures || []).concat(next));
- }
- };
- _asyncEverySeries(fns, apply, function() {
- if (callback) {
- callback.apply(thisArg, arguments);
- }
- });
- } else {
- apply = function(fn) {
- if (Array.isArray(fn)) {
- return _every(fn, apply);
- } else if (typeof fn === "function") {
- return fn.apply(thisArg, fns.captures || []);
- } else if (typeof fn === "string" && self.resource) {
- self.resource[fn].apply(thisArg, fns.captures || []);
- }
- };
- _every(fns, apply);
- }
- };
- Router.prototype.traverse = function(method, path, routes, regexp, filter) {
- var fns = [], current, exact, match, next, that;
- function filterRoutes(routes) {
- if (!filter) {
- return routes;
- }
- function deepCopy(source) {
- var result = [];
- for (var i = 0; i < source.length; i++) {
- result[i] = Array.isArray(source[i]) ? deepCopy(source[i]) : source[i];
- }
- return result;
- }
- function applyFilter(fns) {
- for (var i = fns.length - 1; i >= 0; i--) {
- if (Array.isArray(fns[i])) {
- applyFilter(fns[i]);
- if (fns[i].length === 0) {
- fns.splice(i, 1);
- }
- } else {
- if (!filter(fns[i])) {
- fns.splice(i, 1);
- }
- }
- }
- }
- var newRoutes = deepCopy(routes);
- newRoutes.matched = routes.matched;
- newRoutes.captures = routes.captures;
- newRoutes.after = routes.after.filter(filter);
- applyFilter(newRoutes);
- return newRoutes;
- }
- if (path === this.delimiter && routes[method]) {
- next = [ [ routes.before, routes[method] ].filter(Boolean) ];
- next.after = [ routes.after ].filter(Boolean);
- next.matched = true;
- next.captures = [];
- return filterRoutes(next);
- }
- for (var r in routes) {
- if (routes.hasOwnProperty(r) && (!this._methods[r] || this._methods[r] && typeof routes[r] === "object" && !Array.isArray(routes[r]))) {
- current = exact = regexp + this.delimiter + r;
- if (!this.strict) {
- exact += "[" + this.delimiter + "]?";
- }
- match = path.match(new RegExp("^" + exact));
- if (!match) {
- continue;
- }
- if (match[0] && match[0] == path && routes[r][method]) {
- next = [ [ routes[r].before, routes[r][method] ].filter(Boolean) ];
- next.after = [ routes[r].after ].filter(Boolean);
- next.matched = true;
- next.captures = match.slice(1);
- if (this.recurse && routes === this.routes) {
- next.push([ routes.before, routes.on ].filter(Boolean));
- next.after = next.after.concat([ routes.after ].filter(Boolean));
- }
- return filterRoutes(next);
- }
- next = this.traverse(method, path, routes[r], current);
- if (next.matched) {
- if (next.length > 0) {
- fns = fns.concat(next);
- }
- if (this.recurse) {
- fns.push([ routes[r].before, routes[r].on ].filter(Boolean));
- next.after = next.after.concat([ routes[r].after ].filter(Boolean));
- if (routes === this.routes) {
- fns.push([ routes["before"], routes["on"] ].filter(Boolean));
- next.after = next.after.concat([ routes["after"] ].filter(Boolean));
- }
- }
- fns.matched = true;
- fns.captures = next.captures;
- fns.after = next.after;
- return filterRoutes(fns);
- }
- }
- }
- return false;
- };
- Router.prototype.insert = function(method, path, route, parent) {
- var methodType, parentType, isArray, nested, part;
- path = path.filter(function(p) {
- return p && p.length > 0;
- });
- parent = parent || this.routes;
- part = path.shift();
- if (/\:|\*/.test(part) && !/\\d|\\w/.test(part)) {
- part = regifyString(part, this.params);
- }
- if (path.length > 0) {
- parent[part] = parent[part] || {};
- return this.insert(method, path, route, parent[part]);
- }
- if (!part && !path.length && parent === this.routes) {
- methodType = typeof parent[method];
- switch (methodType) {
- case "function":
- parent[method] = [ parent[method], route ];
- return;
- case "object":
- parent[method].push(route);
- return;
- case "undefined":
- parent[method] = route;
- return;
- }
- return;
- }
- parentType = typeof parent[part];
- isArray = Array.isArray(parent[part]);
- if (parent[part] && !isArray && parentType == "object") {
- methodType = typeof parent[part][method];
- switch (methodType) {
- case "function":
- parent[part][method] = [ parent[part][method], route ];
- return;
- case "object":
- parent[part][method].push(route);
- return;
- case "undefined":
- parent[part][method] = route;
- return;
- }
- } else if (parentType == "undefined") {
- nested = {};
- nested[method] = route;
- parent[part] = nested;
- return;
- }
- throw new Error("Invalid route context: " + parentType);
- };
- Router.prototype.extend = function(methods) {
- var self = this, len = methods.length, i;
- function extend(method) {
- self._methods[method] = true;
- self[method] = function() {
- var extra = arguments.length === 1 ? [ method, "" ] : [ method ];
- self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments)));
- };
- }
- for (i = 0; i < len; i++) {
- extend(methods[i]);
- }
- };
- Router.prototype.runlist = function(fns) {
- var runlist = this.every && this.every.before ? [ this.every.before ].concat(_flatten(fns)) : _flatten(fns);
- if (this.every && this.every.on) {
- runlist.push(this.every.on);
- }
- runlist.captures = fns.captures;
- runlist.source = fns.source;
- return runlist;
- };
- Router.prototype.mount = function(routes, path) {
- if (!routes || typeof routes !== "object" || Array.isArray(routes)) {
- return;
- }
- var self = this;
- path = path || [];
- if (!Array.isArray(path)) {
- path = path.split(self.delimiter);
- }
- function insertOrMount(route, local) {
- var rename = route, parts = route.split(self.delimiter), routeType = typeof routes[route], isRoute = parts[0] === "" || !self._methods[parts[0]], event = isRoute ? "on" : rename;
- if (isRoute) {
- rename = rename.slice((rename.match(new RegExp("^" + self.delimiter)) || [ "" ])[0].length);
- parts.shift();
- }
- if (isRoute && routeType === "object" && !Array.isArray(routes[route])) {
- local = local.concat(parts);
- self.mount(routes[route], local);
- return;
- }
- if (isRoute) {
- local = local.concat(rename.split(self.delimiter));
- local = terminator(local, self.delimiter);
- }
- self.insert(event, local, routes[route]);
- }
- for (var route in routes) {
- if (routes.hasOwnProperty(route)) {
- insertOrMount(route, path.slice(0));
- }
- }
- };
- }(typeof exports === "object" ? exports : window));
|