can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
259 lines (178 loc) • 7.86 kB
Markdown
@function can.route can.route
@group can.route.static static
@inherits can.Map
@download can/route
@test can/route/test.html
@parent canjs
@group can.route.plugins plugins
@link ../docco/route/route.html docco
@description Manage browser history and
client state by synchronizing the window.location.hash with
a [can.Map].
@signature `can.route( template [, defaults] )`
Create a route matching rule.
@param {String} template the fragment identifier to match. The fragment identifier
should start with either a character (a-Z) or colon (:). Examples:
can.route(":foo")
can.route("foo/:bar")
@param {Object} [defaults] an object of default values
@return {can.route}
@body
## Use
Watch this video for an overview of can.route's functionality and an example showing how to connect two tab widgets to the browser's history:
<iframe width="662" height="372" src="https://www.youtube.com/embed/ef0LKDiaPZ0" frameborder="0" allowfullscreen></iframe>
In the following CanJS community we also talk about web application routing:
<iframe width="662" height="372" src="https://www.youtube.com/embed/0Hhuv5Qru9k" frameborder="0" allowfullscreen></iframe>
## Background Information
To support the browser's back button and bookmarking
in an Ajax application, most applications use
the <code>window.location.hash</code>. By
changing the hash (via a link or JavaScript),
one is able to add to the browser's history
without changing the page.
This provides the basics needed to
create history enabled Ajax websites. However,
`can.route` addresses several other needs such as:
- Pretty urls (actually hashes)
- Keeping routes independent of application code
- Listening to specific parts of the history changing
- Setup / Teardown of widgets.
## How it works
<code>can.route</code> is a [can.Map] that represents the
<code>window.location.hash</code> as an
object. For example, if the hash looks like:
#!type=videos&id=5
the data in <code>can.route</code> looks like:
{ type: 'videos', id: 5 }
`can.route` keeps the state of the hash in-sync with the `data` contained within
`can.route`.
## can.Map
`can.route` is a [can.Map]. Understanding
`can.Map` is essential for using `can.route` correctly.
You can listen to changes in an Observe with `bind(eventName, handler(ev, args...))` and
change can.route's properties with
[can.Map.prototype.attr attr].
### Listening to changes in can.route
Listen to changes in history
by [can.Map.prototype.bind bind]ing to
changes in <code>can.route</code> like:
can.route.bind('change', function(ev, attr, how, newVal, oldVal) {
})
- `attr` - the name of the changed attribute
- `how` - the type of Observe change event (add, set or remove)
- `newVal`/`oldVal` - the new and old values of the attribute
### Updating can.route
Create changes in the route data with [can.Map.prototype.attr attr] like:
can.route.attr('type','images');
Or change multiple properties at once like:
can.route.attr({type: 'pages', id: 5}, true)
When you make changes to can.route, they will automatically
change the <code>hash</code>.
## Creating a Route
Use <code>can.route(url, defaults)</code> to create a
route. A route is a mapping from a url to
an object (that is the can.route's state).
In order to map to a specific properties in the url,
prepend a colon to the name of the property like:
can.route( "#!content/:type" )
If no routes are added, or no route is matched,
can.route's data is updated with the [can.deparam deparamed]
hash.
location.hash = "#!type=videos";
// can.route -> {type : "videos"}
Once routes are added and the hash changes,
can.route looks for matching routes and uses them
to update can.route's data.
can.route( "#!content/:type" );
location.hash = "#!content/images";
// can.route -> {type : "images"}
can.route.attr( "type", "songs" )
// location.hash -> "#!content/songs"
Default values can be added to a route:
can.route("content/:type",{type: "videos" });
location.hash = "#!content/"
// can.route -> {type : "videos"}
// location.hash -> "#!content/"
Defaults can also be set on the root page of your app:
can.route( "", { page: "index" } );
location.hash = "#!";
// can.route.attr() -> { page: "index" }
// location.hash -> "#!"
## Initializing can.route
After your application has created all of its routes, call [can.route.ready]
to set can.route's data to match the current hash:
can.route.ready()
## Changing the route.
Typically, you don't set <code>location.hash</code>
directly. Instead, you can change properties on <code>can.route</code>
like:
can.route.attr('type', 'videos')
This will automatically look up the appropriate
route and update the hash.
Often, you want to create links. <code>can.route</code> provides
the [can.route.link] and [can.route.url] helpers to make this
easy:
can.route.link("Videos", {type: 'videos'})
## Demo
The following demo shows the relationship between `window.location.hash`,
routes given to `can.data`,
`can.route`'s data, and events on `can.data`. Most properties
are editable so experiment!
@iframe can/route/demo.html 980
## IE Compatibility
Internet Explorer 6 and 7 does not support `window.onhashchange`.
Even Internet Explorer 8 running in IE7 compatibility mode reports `true`
for `onhashchange` in window, even though the event isn't supported.
If you are using jQuery, you can include Ben Alman's [HashChange Plugin](http://benalman.com/projects/jquery-hashchange-plugin/)
to support the event in the unsupported browser(s).
## Using routes with `can.Control`
Using templated event handlers, it is possible to listen to changes to
`can.route` within `can.Control`. This is convenient as it allows the
control to listen to and make changes whenever the route is modified,
even outside of the control itself.
// create the route
can.route("#!content/:type")
can.Control({
// the route has changed
"{can.route} change": function(ev, attr, how, newVal, oldVal) {
if (attr === "type") {
// the route has a type
}
}
});
### Creating and binding routes with `can.Control.route`
Using [can.Control.route], a builtin plugin to CanJS, cuts down on the amount
of code needed to work with `can.route` in `can.Control`. With this plugin, it is possible
to both create routes and bind to `can.route` at the same time. Instead of creating
several routes to handle changes to __type__ and __id__, write something like this
in a control:
can.Control({
// the route is empty
"route": function(data) {
},
// the route has a type
":type route": function(data) {
},
// the route has a type and id
":type/:id route": function(data) {
}
});
### Getting more specific with the `can.Map.delegate` plugin
Sometimes, you might only want to trigger a function when the route changes
only once, even if the route change gets called multiple times. By using the
[can.Map.delegate] plugin, this is extremely easy. This plugin allows you to
listen to change, set, add, and remove on `can.route`.
If you wanted to, say, show a list of recipes when __type__ was set to recipe
and show a specific recipe when __id__ was set, you could do something like:
can.Control({
"{can.route} type=recipe set":
function( ev, prop, how, newVal, oldVal ) {
// show list of recipes
},
"recipe/:id": function(data) {
// show a single recipe
}
});
If we didn't only listen to when recipe is set, then every time we chose to
show a single recipe, we would create and show the list of recipes again which
would not be very efficient.