Routing

  • page
RecipesRouting  

The following recipes explore using can.route.

History Tabs

This recipe shows how to make a history-based tabs widget and have routes configured independently by can.route.

How it works

The HTML is structured such that each tab button has an <a> element with an href property that matches the id attribute of the tab content panel it should show. This means that even if JavaScript was disabled, clicking a button would send the user to the tab panel (even though HistoryTabs overwrites this behavior).

For example:

<li><a href="#model">can.Model</a></li>

references:

<div id="model" class="tab">

The JavaScript code begins by creating a HistoryTabs control. When a new HistoryTabs instance is created, it gets an attr option like:

new HistoryTabs( '#components',{attr: 'component'});

The attr method is used to configure which part of can.route's data the history tab will be listening to.

When init is called, it hides each tab button's content div, looking up the content div with the tab helper method. It then reads the current active tab with:

var active = can.route.attr(this.options.attr);

It passes that value to the active helper which will hide the old active content (if oldActive is passed) and activate the new active button and show it's content.

HistoryTab updates the active tab by listening when a tab button is clicked with "li click". It prevents the default behavior (which is changing the hash) and updates it's route data attribute with the select tab's id:

can.route.attr(this.options.attr, this.tab(el)[0].id)

HistoryTabs listens to these route changes with "{can.route} {attr}" and activates the new tab.

Configuring Routes

The code ends by configuring the routes and creating the HistoryTabs. Here's what each route rule means:

can.route(":component",{
  component: "model",
  person: "mihael"
});

This matches the empty routes ("","#","#!"), and a single "word" route. If the route is one of the empty routes, the route data will look like: {component: "modal", person: "mihael"}. If it is a single "word" route like "#!view", the data will look like: {component: "view", person: "mihael"}.

can.route(":component/:person",{
  component: "model",
  person: "mihael"
});

This matches two-word routes seperated by a slash ("/"). Each word can be empty. If both words are empty "#!/", the data will look like: {component: "model", person: "mihael"}. If the words are non-empty, that word will replace the default value.

Observe Backed Routes

This recipe shows how to have multiple widgets listening on overlapping parts of the route. The app lets the user select a type of issue, show issues for that type, and select a issue and see details about that issue.

How it works

This functionality is broken down into Nav, Issues, and Details can.controls. Here's how each part works:

Nav creates links using can.route.link that update the hash like:

<%== can.route.link("Critical",{filter: "critical"}) %>

When these are clicked on, they update the route's filter data.

Issues listens to filter changes like:

"{can.route} filter" : function(route, ev, filter){ ... }

It then retrieve's issue with Issue.findAll and renders them into the #issues element.

When an issue is clicked, Issues updates the route's issue data like:

can.route.attr("issue", issue.id)

It listens to changes in issue data and highlights the corresponding issue in the list like:

"{can.route} issue" : function(route, ev, issue){ ... }

Details listens to issue chagnes like:

"{can.route} issue" : function(route, ev, issue){ ... }

And updates the details panel.