Skip to content

Instantly share code, notes, and snippets.

@Raynos
Created August 31, 2014 05:43
Show Gist options
  • Save Raynos/adbf7951bee3fdfe1a65 to your computer and use it in GitHub Desktop.
Save Raynos/adbf7951bee3fdfe1a65 to your computer and use it in GitHub Desktop.
Mercury router
module.exports = clickEvent;
function clickEvent(handler, opts) {
opts = opts || {};
return function clickHandler(ev) {
if (!opts.ctrl && ev.ctrlKey) {
return;
}
if (!opts.meta && ev.metaKey) {
return;
}
if (!opts.rightClick && ev.which === 2) {
return;
}
handler();
ev.preventDefault();
};
}
var h = require('mercury').h;
var clickEvent = require('./click-event.js');
var routeAtom = require('./router.js').atom;
module.exports = anchor;
function anchor(props, text) {
var href = props.href;
props.href = '#';
props['ev-click'] = clickEvent(pushState, {
ctrl: false,
meta: false,
rightClick: false
});
return h('a', props, text);
function pushState() {
routeAtom.set(href);
}
}
var routeMap = require('route-map');
module.exports = routeView;
function routeView(defn, args) {
if (args.base) {
defn = Object.keys(defn)
.reduce(function applyBase(acc, str) {
acc[args.base + str] = defn[str];
return acc;
}, {});
}
var match = routeMap(defn);
var res = match(args.route);
if (!res) {
throw new Error('router: no match found');
}
res.params.url = res.url;
return res.fn(res.params);
}
var mercury = require('mercury');
var source = require('geval/source');
var window = require('global/window');
var document = require('global/document');
var atom = Router.atom =
mercury.value(String(document.location.pathname));
/*
var mercury = require('mercury');
var h = require('mercury').h;
var anchor = require('mercury-route/anchor');
var routeView = require('mercury-route/route-view');
var Router = require('mercury-route/router');
function State() {
var state = mercury.struct({
route: Router()
});
return { state: state }
}
mercury.app(document.body, State().state, render);
function render(state) {
return h('div', [
menu(),
routeView({
'/': renderHome,
'/animals': renderAnimals,
'/animals/:id': renderAnimalItem
}, { route: state.route })
])
}
function menu() {
return h('ul', [
h('li', [
anchor({
href: '/'
}, 'Home')
]),
h('li', [
anchor({
href: '/animals'
}, 'Animals')
])
])
}
*/
module.exports = Router;
function Router() {
var inPopState = false;
var popstates = popstate();
popstates(onPopState);
atom(onRouteSet);
return { state: atom };
function onPopState(uri) {
inPopState = true;
atom.set(uri);
}
function onRouteSet(uri) {
if (inPopState) {
inPopState = false;
return;
}
pushHistoryState(uri);
}
}
function pushHistoryState(uri) {
window.history.pushState(undefined, document.title, uri);
}
function popstate() {
return source(function broadcaster(broadcast) {
window.addEventListener('popstate', onPopState);
function onPopState() {
broadcast(String(document.location.pathname));
}
});
}
@Raynos
Copy link
Author

Raynos commented Nov 16, 2014

You got it spot on :)

  • the anchor module writes to the state atom to trigger a new uri.
  • the route view module is a pure rendering helper function that takes an uri and calls the correct view based on an url pattern.
  • the router itself listens to popstate and updates the state atom with a new uri, when the state atom is mutated it will write to the HTML5 history api to ensure that back & forward works. And it returns the url state atom, this can be plugged into a larger app and you can use the route view helper on it.

The bit that is missing from this is a component interface.

var RouterComponent = function () {
  return Router().state;
};
RouterComponent.render = function (state, opts) {
  return routeView(opts, { route: state });
};
RouterComponent.anchor = anchor;

@kristianmandrup
Copy link

Super!!! I'm trying to integrate much of this in a new framework dragonslayer and I plan to document improve docs for the mercury API in the process..

I like playing with this stuff!! Happy to see I've got it spot in :) Very interesting approach. I plan to integrate this approach with the Crossroads router which is more feature complete.
You are amazing!!

@kristianmandrup
Copy link

Can you show an example for how to use the RouterComponent as well :)
I would think it could be used as a sort of "base class" for any component that wishes to use the Router?

@twilson63
Copy link

@Raynos - I took this gist and converted it into a repo mercury-router https://github.com/twilson63/mercury-router - gave you the credit - just wanted a npm module. if you want it under your account, I will be happy to transfer. Thanks again for the mercury framework and this gist!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment