blob: d0152e625d298e877c3e099320d98bac755c7b01 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var debug = require('debug')('reader:router');
var document = require('global/document');
var extend = require('xtend');
var format = require('format');
var hashbang = require('./hashbang');
var hg = require('mercury');
var parse = require('url').parse;
var pathToRegexp = require('path-to-regexp');
var qs = require('qs');
var source = require('geval/source');
var window = require('global/window');
module.exports = {
state: state
};
function state(options) {
options = extend({ routes: {} }, options);
debug('init: %o', options);
var atom = hg.state({
routes: hg.varhash({}),
href: hg.value(options.href || ''),
query: hg.value(null),
params: hg.value({}),
route: hg.value(null)
});
// Map keys in options.routes and map to regular expresions which can be
// matched against later.
for (var key in options.routes) { // jshint ignore: line
var pattern = options.routes[key];
var keys = [];
// The pattern "*" should be greedy.
var path = (pattern === '*') ? '(.*)' : pattern;
atom.routes.put(key, {
pattern: pattern,
keys: keys,
re: pathToRegexp(path, keys)
});
}
// Treat changes to window.location as a user event which should trigger the
// match channel.
popstate(function onpopstate(href) {
match(atom, { href: href });
});
debug('firing initial route');
match(atom, {
href: String(document.location.href)
});
return atom;
}
function match(state, data) {
if (state.href() === data.href) {
debug('no update to href, skipping');
return;
}
var url = parse(data.href);
var hash = (url.hash) ? url.hash : hashbang(url.pathname);
var href = require('url').format({
protocol: url.protocol,
host: url.host,
pathname: '/',
hash: hash
});
state.href.set(href);
var routes = state.routes();
var keys = Object.keys(routes);
var length = keys.length;
var _match;
for (var i = 0; i < length; i++) {
var key = keys[i];
var route = state.routes.get(key);
_match = hash.match(route.re);
if (!_match) {
continue;
}
state.query.set(qs.parse(url.query));
state.route.set(route.pattern);
var params = {};
var ki = route.keys.length;
while (ki--) {
var param = route.keys[ki].name;
var value = _match[ki+1];
params[param] = value;
}
state.params.set(params);
break;
}
if (! _match) {
var template = 'No route defined for "%s". To prevent this error add a' +
'route for "*" .';
throw new Error(format(template, data.href));
}
}
function popstate(listener) {
var event = source(lambda);
event(listener);
function lambda(broadcast) {
window.addEventListener('popstate', function(event) {
var href = String(document.location.href);
broadcast(href);
});
}
}