Router
Simple Routes - Containers - Resource Based Routes - Singular Resources - Nested Resources - Namespaces - Adding Custom Actions - Changing Root Container - Vars - Flash - Navigation and Redirection - Component Replacement - Query Rarams - Before-Navigation Callbacks
Simple Routes
Cape.JS’s router reacts to the changes of URL hash fragment and replace the component mounted on the target node.
The following example illustrates the basic concept of router and routes.
index.html
<div>
<a href="#">Top</a>
<a href="#about">About</a>
<a href="#help">Help</a>
</div>
<div id="main"></div>
<script src="./components.js"></script>
<script src="./router.js"></script>
components.js
var TopPage = Cape.createComponentClass({
render: function(m) {
m.p('This is the top page.')
}
});
var AboutPage = Cape.createComponentClass({
render: function(m) {
m.p('This is the about page.')
}
});
var HelpPage = Cape.createComponentClass({
render: function(m) {
m.p('This is the help page.')
}
});
router.js
var router = new Cape.Router();
router.draw(function(m) {
m.root('top_page');
m.page('about', 'about_page');
m.page('help', 'help_page');
})
router.mount('main');
router.start();
m.root('top_page')
connects the empty hash to the component TopPage
so that the browser displays the top page when we open this site.
m.page('about', 'about_page')
connects the hash #about
to the component AboutPage
.
When we click the ‘About’ link, the TopPage
component is removed and
the AboutPage
component gets mounted.
Each connection between a hash and a component is called route. The router continues to watch the changes of URL hash and switches components according to the routes.
You can find the source code of working demo on https://github.com/capejs/capejs/tree/master/demo/simple_routes.
Containers
When the number of components is getting larger, you may want to organize them in a hyerarchical structure. In this case, you can create some objects to contain component classes as follows:
var Top = {};
Top.IndexPage = Cape.createComponentClass({
render: function(m) {
m.p('This is the top page.')
}
});
Top.AboutPage = Cape.createComponentClass({
render: function(m) {
m.p('This is the about page.')
}
});
Top.HelpPage = Cape.createComponentClass({
render: function(m) {
m.p('This is the help page.')
}
});
We call objects of this kind containers.
When you define routes to classes under a container, you should connect the container’s name and the class name with a dot like this:
var router = new Cape.Router();
router.draw(function(m) {
m.root('top.index_page');
m.page('about', 'top.about_page');
m.page('help', 'top.help_page');
})
Resource Based Routes
When you create a user interface for CRUD operations on a database table, say articles
,
you will need four pages typically:
- A page for listing articles
- A page for showing the details of an existing article
- A page to add a new article
- A page to update an existing article
As a framework, Cape.JS recommends you to create following routes for these pages:
articles => Articles.List
articles/:id => Articles.Item
articles/new => Articles.Form
articles/:id/edit => Articles.Form
In line with this, you will create three component classes under a container called Articles
and define routes as follows:
var router = new Cape.Router();
router.draw(function(m) {
m.page('articles', 'articles.list');
m.page('articles/:id', 'articles.item');
m.page('articles/new', 'articles.form');
m.page('articles/:id/edit', 'articles.form');
})
Using many
method, you can define them in much easier way, though.
var router = new Cape.Router();
router.draw(function(m) {
m.many('articles');
})
The routes defined by the above code are summarized in the next table:
Hash pattern | Container | Component | Resource | Action |
---|---|---|---|---|
articles | Articles | List | articles | index |
articles/:id | Articles | Item | articles | show |
articles/new | Articles | Form | articles | new |
articles/:id/edit | Articles | Form | articles | edit |
The fourth and fifth rows of the table contain the resource name and action name of routes. In Cape.JS, unlike in Ruby on Rails, the concepts of resource and action don’t play important role, but we use them occasionally.
Firstly, we specify action names to the only
and except
option in order
to exclude some routes from definition:
var router = new Cape.Router();
router.draw(function(m) {
m.many('articles', except: [ 'new', 'edit' ]);
m.many('announcement', only: [ 'index' ]);
})
Secondly, we want to know the current resource and action names in order to control the flow of processing when we render components:
Articles.Form = Cape.createComponentClass({
render: function(m) {
if (router.action === 'new') {
// ...
}
else {
// ...
}
}
});
Singular Resources
When you want to define routes for a resource that can have only zero or
one instance, you should use one
method as follows:
var router = new Cape.Router();
router.draw(function(m) {
m.one('account');
})
The routes defined by the above code are summarized in the next table:
Hash pattern | Container | Component | Resource | Action |
---|---|---|---|---|
account | Account | Content | account | show |
account/new | Account | Form | account | new |
account/edit | Account | Form | account | edit |
Note that the naming convension is different from that of Ruby on Rails. The container’s name is singular, not plural.
Nested Resources
You can define resources which are logically children of other resources as follows:
var router = new Cape.Router();
router.draw(function(m) {
m.many('articles', { only: [] }, function(m) {
m.many('comments')
});
})
The routes defined by the above code are summarized in the next table:
Hash pattern | Container | Component | Resource | Action |
---|---|---|---|---|
articles/:article_id/comments | Comments | List | articles/comments | index |
articles/:article_id/comments/:id | Comments | Item | articles/comments | show |
articles/:article_id/comments/new | Comments | Form | articles/comments | new |
articles/:article_id/comments/:id/edit | Comments | Form | articles/comments | edit |
Note that you should define Comments.List
, Comments.Item
and Comments.Form
.
They are not Articles.Comments.List
, Articles.Comments.Item
and Articles.Comments.Form
.
Namespaces
You can define resources under a namespace as follows:
var router = new Cape.Router();
router.draw(function(m) {
m.namespace('admin', function(m) {
m.many('articles');
})
})
The routes defined by the above code are summarized in the next table:
Hash pattern | Container | Component | Namespace | Resource | Action |
---|---|---|---|---|---|
admin/articles | Admin.Articles | List | admin | articles | index |
admin/articles/:id | Admin.Articles | Item | admin | articles | show |
admin/articles/new | Admin.Articles | Form | admin | articles | new |
admin/articles/:id/edit | Admin.Articles | Form | admin | articles | edit |
Adding Custom Actions
Adding Collection Routes
To add a collection route (a route which deals with multiple items), use collection
method:
var router = new Cape.Router();
router.draw(function(m) {
m.many('articles', function(m) {
m.collection 'draft'
});
})
This defines a route from articles/draft
to Articles.Draft
.
Adding Member Routes
To add a member route (a route which deals with a specific item), use member
method:
var router = new Cape.Router();
router.draw(function(m) {
m.many('articles', function(m) {
m.member 'info'
});
})
This defines a route from articles/:id/info
to Articles.Info
.
Adding Routes for Additional New Actions
To add an alternate new action, use new
method:
var router = new Cape.Router();
router.draw(function(m) {
m.many('articles', function(m) {
m.new 'preview'
});
})
This defines a route from articles/new/preview
to Articles.Preview
.
Adding Additional Routes to Singular Resources
To add an additional route to singular resources, use view
method:
var router = new Cape.Router();
router.draw(function(m) {
m.one('account', function(m) {
m.view 'photo'
});
})
This defines a route from account/photo
to Account.Photo
.
Adding Routes for Additional New Actions of Singular Resources
To add an alternate new action to singular resources, use new
method:
var router = new Cape.Router();
router.draw(function(m) {
m.one('account', function(m) {
m.new 'preview'
});
})
This defines a route from account/new/preview
to Account.Preview
.
Changing Root Container
By default, the root container is window
object.
In other words, variables for components and containers are defined globally.
But you can change it by passing an object to the Cape.Router
’s constructor:
var MyApp = {};
var router = new Cape.Router(MyApp);
router.draw(function(m) {
m.root('top_page')
m.many('articles');
})
Then the component class for root page becomes MyApp.TopPage
,
and the container for the articles
resource becomes MyApp.Articles
.
Vars
Routers have a property named vars
, which developers can store arbitrary data to.
For example, you can store attributes of the current user:
var router = new Cape.Router();
router.vars.current_user = { id: 123, name: 'john', privileged: true }
You can use this data in the render
method of a component like this:
render: function(m) {
if (router.vars.current_user.privileged)
m.attr(onclick: function(e) { this.deleteItem() } );
else
m.class('disabled')
m.button('Delete');
}
Flash
Routers have a property named flash
, which developers can store arbitrary data to,
but is erased after each navigation.
For example, you can store the alert message to be displayed in the next page:
var router = new Cape.Router();
router.flash.alert = "You can't delete this item.";
You can use this data in the render
method of a component like this:
render: function(m) {
if (router.flash.alert !== undefined) {
m.div(router.flash.alert, { class: 'alert' });
}
}
Navigation and Redirection
This section is not yet written.
Component Replacement
This section is not yet written.
Query Params
This section is not yet written.
Before-Navigation Callbacks
You can register one or more callbacks which are executed before each navigation.
Here is an example:
var router = new Cape.Router();
router.beforeNavigation(function(hash) {
return new Promise(function(resolve, reject) {
$.ajax({
type: 'GET',
url: '/session'
}).done(function(data) {
if (data.text == 'OK') {
router.vars.currentUser = data.user;
resolve(hash);
}
else {
router.vars.currentUser = null;
resolve('login');
}
}).error(function() {
reject(Error('ERROR'));
});
});
});
router.errorHandler(function(err) {
router.show(Errors.NetworkError);
});
In the above code, we get attributes of the user who is signing in to this application through an Ajax request.
The resolve
and reject
are functions.
If we call the resolve
passing a hash as the first argument,
the process continues to the next callback which takes this hash as its first argument.
When all callbacks are executed successfully, the hash which is specified by the last callback
determines the component to be mounted.
If we call the reject
method, the next and subsequent callbacks are skipped and
the error handler is executed if it exists.
The show
method of router mounts the specified component (Errors.NetworkError
)
immediately without executing before-navigation callbacks.