-
-
Save Raynos/adbf7951bee3fdfe1a65 to your computer and use it in GitHub Desktop.
| 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)); | |
| } | |
| }); | |
| } |
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;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!!
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?
@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!
Trying to understand...
routeAtom.set(href);usesmercury.value- ah, to be found in https://github.com/Raynos/observ, so it is a generic immutable observable I guess. I expect thatatom(onRouteSet);inRoutermeans it will callonRouteSet(uri)with thehrefwhich will in turn callpushHistoryState(uri);?Then there is this part which also needs a bit of explanation: what is
broadcast?window.popstate- https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history, simply triggered when going back in history, which setsdocument.location.pathnameto previous state, which we then broadcast somehow...broadcast is available from within the context of
sourcefrom geval at https://github.com/Raynos/gevalWhich uses the
broadcastinEventviatupleBut who is listening? Looks like this logic sets up the
onPopStateto listen for uri's popped fromwindow.history. Clever!And the actual routing is performed via
routeView