Name: router.js
Owner: Crowdstrike
Description: null
Created: 2013-01-11 18:47:14.0
Updated: 2015-10-30 17:23:03.0
Pushed: 2013-01-17 01:04:08.0
Homepage: null
Size: 189
Language: JavaScript
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
router.js
is a lightweight JavaScript library (under 1k!)
that builds on
route-recognizer
to provide an API for handling routes.
In keeping with the Unix philosophy, it is a modular library that does one thing and does it well.
Create a new router:
router = new Router();
Add a simple new route description:
er.map(function(match) {
tch("/posts/:id").to("showPost");
tch("/posts").to("postIndex");
tch("/posts/new").to("newPost");
Add your handlers:
er.handlers.showPost = {
serialize: function(params) {
return App.Post.find(params.id);
tup: function(post) {
// render a template with the post
er.handlers.postIndex = {
serialize: function(params) {
return App.Post.findAll();
tup: function(posts) {
// render a template with the posts
er.handlers.newPost = {
tup: function(post) {
// render a template with the post
Use another modular library to listen for URL changes, and tell the router to handle a URL:
atcher.onUpdate(function(url) {
uter.handleURL(url);
The router will parse the URL for parameters and then pass
the parameters into the handler's deserialize
method. It
will then pass the return value of deserialize
into the
setup
method. These two steps are broken apart to support
async loading via promises (see below).
To transition into the state represented by a handler without
changing the URL, use router.transitionTo
:
er.transitionTo('showPost', post);
If you pass an extra parameter to transitionTo
, as above,
the router will pass it to the handler's serialize
method to extract the parameters. Let's flesh out the
showPost
handler:
er.handlers.showPost = {
when coming in from a URL, convert parameters into
an object
serialize: function(params) {
return App.Post.find(params.id);
when coming in from `transitionTo`, convert an
object into parameters
rialize: function(object) {
return { id: post.id };
tup: function(post) {
// render a template with the post
As a modular library, router.js
does not express an
opinion about how to reflect the URL on the page. Many
other libraries do a good job of abstracting hash
and
pushState
and working around known bugs in browsers.
The router.updateURL
hook will be called to give you
an opportunity to update the browser's physical URL
as you desire:
er.updateURL = function(url) {
ndow.location.hash = url;
No matter whether you go to a handler via a URL change
or via transitionTo
, you will get the same behavior.
If you enter a state represented by a handler through a URL:
If you enter a state via transitionTo
:
setup
This means that you can be sure that your application's top-level objects will always be in sync with the URL, no matter whether you are extracting the object from the URL or if you already have the object.
When extracting an object from the parameters, you may need to make a request to the server before the object is ready.
You can easily achieve this by returning a promise
from your deserialize
method. Because jQuery's Ajax
methods already return promises, this is easy!
er.handlers.showPost = {
serialize: function(params) {
return $.getJSON("/posts/" + params.id).then(function(json) {
return new App.Post(json.post);
});
rialize: function(post) {
return { id: post.get('id') };
tup: function(post) {
// receives the App.Post instance
You can register a loading
handler for router.js
to
call while it waits for promises to resolve:
er.handlers.loading = {
no deserialize or serialize because this is not
a handler for a URL
tup: function() {
// show a loading UI
You can nest routes, and each level of nesting can have its own handler.
If you move from one child of a parent route to another, the parent will not be set up again unless it deserializes to a different object.
Consider a master-detail view.
er.map(function(match) {
tch("/posts").to("posts", function(match) {
match("/").to("postIndex");
match("/:id").to("showPost");
;
er.handlers.posts = {
serialize: function() {
return $.getJSON("/posts").then(function(json) {
return App.Post.loadPosts(json.posts);
});
no serialize needed because there are no
dynamic segments
tup: function(posts) {
var postsView = new App.PostsView(posts);
$("#master").append(postsView.el);
er.handlers.postIndex = {
tup: function() {
$("#detail").hide();
er.handlers.showPost = {
serialize: function(params) {
return $.getJSON("/posts/" + params.id, function(json) {
return new App.Post(json.post);
});
er.handlers.loading = {
tup: function() {
$("#content").hide();
$("#loading").show();
it: function() {
$("#loading").hide();
$("#content").show();
You can also use nesting to build nested UIs, setting up the outer view when entering the handler for the outer route, and setting up the inner view when entering the handler for the inner route.
Routes at any nested level can deserialize parameters into a
promise. The router will remain in the loading
state until
all promises are resolved. If a parent state deserializes
the parameters into a promise, that promise will be resolved
before a child route is handled.
When the URL changes and a handler becomes active, router.js
invokes a number of callbacks:
transitionTo
. A context is consumed if the handler's
route fragment has a dynamic segment and the handler has a
deserialize method.For handlers that are no longer active after a change,
router.js
invokes the exit callback.
The order of callbacks are:
For example, consider the following tree of handlers. Each handler is followed by the URL segment it handles.
dex ("/")
posts ("/posts")
|-showPost ("/:id")
|-newPost ("/new")
|-editPost ("/edit")
about ("/about/:id")
Consider the following transitions:
/posts/1
.deserialize
callback on the
index
, posts
, and showPost
handlersenter
callback on the samesetup
callback on the samenewPost
exit
callback on showPost
enter
callback on newPost
setup
callback on newPost
about
with a specified
context objectexit
callback on newPost
and posts
serialize
callback on about
enter
callback on about
setup
callback on about
You can also nest without extra handlers, for clarity.
For example, instead of writing:
er.map(function(match) {
tch("/posts").to("postIndex");
tch("/posts/new").to("newPost");
tch("/posts/:id/edit").to("editPost");
tch("/posts/:id").to("showPost");
You could write:
er.map(function(match) {
tch("/posts", function(match) {
match("/").to("postIndex");
match("/new").to("newPost");
match("/:id", function(match) {
match("/").to("showPost");
match("/edit").to("editPost");
});
;
Typically, this sort of nesting is more verbose but
makes it easier to change patterns higher up. In this
case, changing /posts
to /pages
would be easier
in the second example than the first.
Both work identically, so do whichever you prefer.
When handlers are active, you can trigger events on the router. The router will search for a registered event backwards from the last active handler.
You specify events using an events
hash in the
handler definition:
lers.postIndex = {
ents: {
expand: function(handler) {
// the event gets a reference to the handler
// it is triggered on as the first argument
}
For example:
er.map(function(match) {
tch("/posts").to("posts", function(match) {
match("/").to("postIndex");
match("/:id").to("showPost");
match("/edit").to("editPost");
;
er.handlers.posts = {
ents: {
collapseSidebar: function(handler) {
// do something to collapse the sidebar
}
er.handlers.postIndex = {};
er.handlers.showPost = {};
er.handlers.editPost = {
ents: {
collapseSidebar: function(handler) {
// override the collapseSidebar handler from
// the posts handler
}
rigger the event
er.trigger('collapseSidebar');
When at the postIndex
or showPost
route, the collapseSidebar
event will be triggered on the posts
handler.
When at the editPost
route, the collapseSidebar
event
will be triggered on the editPost
handler.
When you trigger an event on the router, router.js
will
walk backwards from the last active handler looking for
an events hash containing that event name. Once it finds
the event, it calls the function with the handler as the
first argument.
This allows you to define general event handlers higher up in the router's nesting that you override at more specific routes.
router.js
uses route-recognizer
under the hood, which
uses an NFA
to match routes. This means that even somewhat elaborate
routes will work:
er.map(function(match) {
this will match anything, followed by a slash,
followed by a dynamic segment (one or more non-
slash characters)
tch("/*page/:location").to("showPage");
If there are multiple matches, route-recognizer
will
prefer routes with fewer dynamic segments, so
/posts/edit
will match in preference to /posts/:id
if both match.
router.js
is functional today. I plan to add more features
before a first official release:
failure
handler if any of the promises are rejected~~exit
callback on a handler when the app navigates
to a page no longer represented by the handler~~router.js
will be the basis for the router in Ember.js.