Skip to content

Instantly share code, notes, and snippets.

@steos
Last active August 29, 2015 14:16
Show Gist options
  • Save steos/0b24fde4eaf6965c0b67 to your computer and use it in GitHub Desktop.
Save steos/0b24fde4eaf6965c0b67 to your computer and use it in GitHub Desktop.
bidirectional declarative microrouter (ES6)
'use strict';
import {first, rest, last, trimLeft, partial, partialRight, omit, dropRight,
zipObject, isObject, assign, ary, map, template} from 'lodash';
const placeHolderRegex = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
var trimLeadingColon = ary(partialRight(trimLeft, ':'), 1);
function assembleRoute(segment, routes) {
var renderPath = template(segment, {interpolate: placeHolderRegex});
return assign(renderPath, assemble(routes || {}, segment))
}
function assemble(routes, prefix = '') {
return zipObject(map(routes, (val, key) => {
let name = isObject(val) ? val.name : val;
return [name, assembleRoute(prefix + key, isObject(val) ? val.routes : null)];
}));
}
function match(routes, path) {
for (var segment in routes) {
if (!routes.hasOwnProperty(segment)) continue;
let spec = routes[segment];
let name = isObject(spec) ? spec.name : spec;
let payload = isObject(spec) ? omit(spec, ['name', 'routes']) : {};
let subRoutes = isObject(spec) ? spec.routes : null;
let paramKeys = (segment.match(placeHolderRegex) || []).map(trimLeadingColon);
let re = new RegExp('^' + segment.replace(placeHolderRegex, '([^/]+)') + '(/.+)?$');
let matched = path.match(re);
if (matched) {
let remainingPath = last(matched);
let params = zipObject(paramKeys, dropRight(rest(matched)));
if (subRoutes && remainingPath) {
let subMatch = match(subRoutes, remainingPath);
if (subMatch) {
let [subName, subParams, subRemaining, subPayload] = subMatch;
return [[name].concat(subName), assign(params, subParams), subRemaining, subPayload];
}
}
return [[name], params, remainingPath, payload];
}
}
return null;
}
export default function(routes) {
let router = assemble(routes);
function resolve(path) {
let matched = match(routes, path);
if (matched) {
let [keys, params, remaining, payload] = matched;
let route = keys.reduce((obj, key) => obj[key], router);
let result = {params, remaining, route, name: keys.join('.')};
return isObject(payload) ? assign(payload, result) : result;
}
return null;
}
return assign(resolve, router);
}
import router from './router';
let route = router({
'/blub': { // key is the path to match
name: 'blub', // must have name for bidirectional routing (must be unique among siblings)
myValue: blub, // properties other than "name" and "routes" are for user data
routes: { // "routes" property contains nested subroutes. can nest infinitely.
'/edit/:id': { // can use placeholders to capture params
name: 'edit',
myValue: blubEdit
}
}
},
'/edit/:foo': {
name: 'edit',
myValue: edit
}
});
route.blub(); // => "/blub"
route.blub.edit({id: 42}); // => "/blub/edit/42"
route.edit({foo: 'bar'}); // => "/edit/bar"
route('/blub'); // => {myValue: blub, params: {}, name: 'blub', remaining: ''}
route('/blub/edit/23'); // => {myValue: blubEdit, params: {id: '23'}, name: 'blub.edit', remaining: ''}
route('/edit/bar'); // => {myValue: edit, params: {foo: 'bar'}, name: 'edit', remaining: ''}
route('/blub/blah'); // => {... name: 'blub', remaining: '/blah'}
route('/blub/edit/23/foo/bar'); // => {... name: 'blub.edit', params: {id: '23'}, remaining: '/foo/bar'}
route('/blubb'); // => null
route('/edit/bar/'); // => null
route('/foo'); // => null
// captured params will all be merged into one object
router({
'/get/:what/:id': {
name: 'foo',
routes: {
'/bar/:barId': 'bar'
}
}
})('/get/foo/42/bar/23'); // => {... params: {what: 'foo', id: '42', barId: '23'}, name: 'foo.bar' }
// child route params overwrite parents
router({
'/foo/:id': {
name: 'foo',
routes: {
'/bar/:id': 'bar'
}
}
})('/foo/1/bar/2'); // => {... params: {id: '2'}, name: 'foo.bar' }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment