Status | beta |
A router for khufu-based projects.
It's not pattern based as many other router, but allows to create hierarchy and compose things.
Features:
- Has a redux-store interface, so integrates with khufu easily
- Address bar is always up to date including all current knobs and
text fields that should be visible in the url as
?query=fields
- Keeps track of what controls are actually visible to user automatically and which have default value, so the URL isn't overloaded by useless things
npm install khufu-routing@0.1.3 --save
First create a router:
import {Router} from 'khufu-routing'
let router = new Router(window);
Then take a note that Router
and subrouter classes are actually stores not
reducers (i.e. objects having getState()
and dispatch()
methods not pure
functions), so you need to skip createStore(..)
for them:
let khufu = attach(document.getElementById('app'), main(router), {
store(reducer, middleware, state) {
if(typeof reducer == 'function') {
return createStore(reducer, state,
applyMiddleware(...middleware))
} else {
return reducer
}
}
})
And then subscribe for router changes:
router.subscribe(khufu.queue_render)
Router splits paths on slashes and additionally allows to add query parameters.
To match next path element against constant use at()
, for example
to match /site/articles/new
path you would have the following statements:
if let r1 = router.at('site'):
if let r2 = r1.at('articles'):
if let r3 = r2.at('new'):
"new article"
In reality it will not be the same block of code, for example you will match
for site
in main.khufu
, for articles
in site.khufu
and for new
in site/articles.khufu
. Additionally each of the if
statements usually has
some HTML markup.
To match the value against the rule, rather than constant use value(func)
:
import {int} from './validators'
if let r1 = router.at('articles'):
if let [article_id, r2] = r1.value(int):
`integer article id: ${article_id}`
If func
returns anything other than null
or undefined
the value()
method returns pair: result of the func
and subrouter.
Both at()
and value()
return subrouters that are basis for three things:
- Matching next path elements
- Generating URLs relative to the subrouter
- Adding query parameters
The no. 2 and 3 are explained below.
Sometimes you want additional things expressed as the query parameter. Create
a store for it using .query(name)
. Here are examples of two parameters:
<div>
store @edit_mode = router.query('edit')
if let r1 = router.at('articles'):
<div>
store @filter = r1.query('filter')
<input type="text" value=@filter>
For the URL /articles?edit=true&filter=xxx
the value of @edit_mode
store
will be true
and the value of @filter
store will be xxx
.
To attach actions to them there are two action creators:
set()
input()
The peculiarity of the input
method is that consequent input isn't added
to the history as separate pages. I.e. as user types text the URL bar is
updated but there is no history entry for every key press. Here is how it's
used:
import {set, input} from 'khufu-routing'
value main(router):
<div>
store @edit_mode = router.query('edit')
<button>
link {click} set('true') -> @edit_mode
"start edit"
if let r1 = router.at('articles'):
<div>
store @filter = r1.query('filter')
<input type="text" value=@filter>
link {input} input(event) -> @filter
Note also that queries
are attached to different routers, this means if
users goes to a relative path ..
from /articles
, i.e. to root of the
site all of it's attached queries stay there, but on child routes are not.
I.e. going to the ..
from /articles?edit=true&filter=xxx
yields
/?edit=true
.
There is only one tool for generating url, it's specifying path, relative to the URL of the router (note: not relative to the current page) for example:
if let r1 = router.at('site'):
<a href=r1.rel('articles')> "List" # -> /articles
if let r2 = r1.at('articles'):
<a href=r2.rel('123')> "Epic Story" # -> /articles/123
<a href=r2.rel('../news')> "News" # -> "/news"
if let r3 = r2.at('new'):
<a href=r2.rel('../123')> "Epic Story" # -> "/articles/123"
<a href=r2.rel('save')> "Save Article" # -> "/articles/new/save"
"new article"
Also query arguments added to the URI as described above.
For navigation over links use go()
action creator sending either
link click event or relative path to the appropriate router:
import {set, input} from 'khufu-routing'
value main(router):
<div>
<a href=router.rel('articles')>
link {click} go(event) -> @router
"List"
if let r1 = router.at('articles'):
<a href=r1.rel('123')>
link {click} go(event) -> @r1
"Epic story"
<button>
link {click} go('new') -> @r1
New
Licensed under either of
- Apache License, Version 2.0, (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.