gallery.accords-library.com/client/js/router.js

294 lines
7.1 KiB
JavaScript
Raw Normal View History

'use strict';
// modified page.js by visionmedia
// - removed unused crap
// - refactored to classes
const pathToRegexp = require('path-to-regexp');
const clickEvent = document.ontouchstart ? 'touchstart' : 'click';
let location = window.history.location || window.location;
const base = '';
let prevContext = null;
function _decodeURLEncodedURIComponent(val) {
if (typeof val !== 'string') {
return val;
}
return decodeURIComponent(val.replace(/\+/g, ' '));
}
function _isSameOrigin(href) {
let origin = location.protocol + '//' + location.hostname;
if (location.port) {
origin += ':' + location.port;
}
return href && href.indexOf(origin) === 0;
}
class Context {
constructor(path, state) {
if (path[0] === '/' && path.indexOf(base) !== 0) {
path = base + path;
}
this.canonicalPath = path;
this.path = path.replace(base, '') || '/';
this.title = document.title;
this.state = state || {};
this.state.path = path;
this.params = {};
}
pushState() {
history.pushState(this.state, this.title, this.canonicalPath);
}
save() {
history.replaceState(this.state, this.title, this.canonicalPath);
}
};
class Route {
constructor(path, options) {
options = options || {};
this.path = (path === '*') ? '(.*)' : path;
this.method = 'GET';
this.regexp = pathToRegexp(this.path, this.keys = [], options);
}
middleware(fn) {
return (ctx, next) => {
if (this.match(ctx.path, ctx.params)) {
return fn(ctx, next);
}
next();
};
}
match(path, params) {
const keys = this.keys;
const qsIndex = path.indexOf('?');
const pathname = ~qsIndex ? path.slice(0, qsIndex) : path;
const m = this.regexp.exec(decodeURIComponent(pathname));
if (!m) {
return false;
}
for (let i = 1, len = m.length; i < len; ++i) {
const key = keys[i - 1];
const val = _decodeURLEncodedURIComponent(m[i]);
if (val !== undefined || !(hasOwnProperty.call(params, key.name))) {
params[key.name] = val;
}
}
return true;
}
};
class Router {
constructor() {
this._callbacks = [];
this._exits = [];
this._current = '';
}
enter(path) {
const route = new Route(path);
for (let i = 1; i < arguments.length; ++i) {
this._callbacks.push(route.middleware(arguments[i]));
}
}
exit(path, fn) {
const route = new Route(path);
for (let i = 1; i < arguments.length; ++i) {
this._exits.push(route.middleware(arguments[i]));
}
}
start() {
if (this._running) {
return;
}
this._running = true;
this._onPopState = _onPopState(this);
this._onClick = _onClick(this);
window.addEventListener('popstate', this._onPopState, false);
document.addEventListener(clickEvent, this._onClick, false);
const url = location.pathname + location.search + location.hash;
this.replace(url, null, true);
}
stop() {
if (!this._running) {
return;
}
this._current = '';
this._running = false;
document.removeEventListener(clickEvent, this._onClick, false);
window.removeEventListener('popstate', this._onPopState, false);
}
show(path, state, push) {
const ctx = new Context(path, state);
this._current = ctx.path;
this.dispatch(ctx);
if (ctx.handled !== false && push !== false) {
ctx.pushState();
}
return ctx;
}
replace(path, state, dispatch) {
var ctx = new Context(path, state);
this._current = ctx.path;
ctx.save();
if (dispatch) {
this.dispatch(ctx);
}
return ctx;
}
dispatch(ctx) {
const prev = prevContext;
let i = 0;
let j = 0;
prevContext = ctx;
const nextExit = () => {
const fn = this._exits[j++];
if (!fn) {
return nextEnter();
}
fn(prev, nextExit);
};
const nextEnter = () => {
const fn = this._callbacks[i++];
if (ctx.path !== this._current) {
ctx.handled = false;
return;
}
if (!fn) {
return this._unhandled(ctx);
}
fn(ctx, nextEnter);
};
if (prev) {
nextExit();
} else {
nextEnter();
}
}
_unhandled(ctx) {
if (ctx.handled) {
return;
}
let current = location.pathname + location.search;
if (current === ctx.canonicalPath) {
return;
}
router.stop();
ctx.handled = false;
location.href = ctx.canonicalPath;
}
};
const _onPopState = router => {
let loaded = false;
if (document.readyState === 'complete') {
loaded = true;
} else {
window.addEventListener(
'load',
() => {
setTimeout(() => {
loaded = true;
}, 0);
});
}
return e => {
if (!loaded) {
return;
}
if (e.state) {
const path = e.state.path;
router.replace(path, e.state, true);
} else {
router.show(
location.pathname + location.hash,
undefined,
false);
}
};
};
const _onClick = router => {
return e => {
if (1 !== _which(e)) {
return;
}
if (e.metaKey || e.ctrlKey || e.shiftKey) {
return;
}
if (e.defaultPrevented) {
return;
}
let el = e.path ? e.path[0] : e.target;
while (el && el.nodeName !== 'A') {
el = el.parentNode;
}
if (!el || el.nodeName !== 'A') {
return;
}
if (el.hasAttribute('download') ||
el.getAttribute('rel') === 'external') {
return;
}
const link = el.getAttribute('href');
if (el.pathname === location.pathname && (el.hash || '#' === link)) {
return;
}
if (link && link.indexOf('mailto:') > -1) {
return;
}
if (el.target) {
return;
}
if (!_isSameOrigin(el.href)) {
return;
}
let path = el.pathname + el.search + (el.hash || '');
const orig = path;
if (path.indexOf(base) === 0) {
path = path.substr(base.length);
}
if (base && orig === path) {
return;
}
e.preventDefault();
router.show(orig);
};
};
function _which(e) {
e = e || window.event;
return e.which === null ? e.button : e.which;
}
Router.prototype.Context = Context;
Router.prototype.Route = Route;
module.exports = new Router();