-
-
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);
inRouter
means it will callonRouteSet(uri)
with thehref
which 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.pathname
to previous state, which we then broadcast somehow...broadcast is available from within the context of
source
from geval at https://github.com/Raynos/gevalWhich uses the
broadcast
inEvent
viatuple
But who is listening? Looks like this logic sets up the
onPopState
to listen for uri's popped fromwindow.history
. Clever!And the actual routing is performed via
routeView