Diving into CanJS: Part 2

This is part two of a three part series that will teach you how to build a contacts manager application in JavaScript using CanJS and jQuery. When you're done with this tutorial, you'll have all you need to build your own JavaScript applications using CanJS!

In part one, you created the Models, Views and Controls needed to display contacts and used fixtures to simulate a REST service.

In this part, you will:

Create a Control and View to display categories.

Listen to events using a Control.

Use routing to filter contacts.

You'll be adding to the source files from part one, so if you haven't done so already, go catch up first. I'll be here when you're ready.

Setting Up Routing

Routing helps manage browser history and client state in single page JavaScript applications.

Routing helps manage browser history and client state in single page JavaScript applications. The hash in the URL contains properties that an application reads and writes. Various parts of the app can listen to these changes and react accordingly, usually updating parts of the current page without loading a new one.

can.route is a special observable that updates and responds to changes in window.location.hash. Use can.route to map URLs to properties, resulting in pretty URLs like #!filter/all. If no routes are defined, the hash value is just serialized into URL encoded notation like #!category=all.

In this application, routing will be used to filter contacts by category. Add the following code to your contacts.js file:

can.route( 'filter/:category' )
can.route('', {category: 'all' })

The first line creates a route with a category property that your application will be able to read and write. The second line creates a default route, that sets the category property to all.

Working With a List Of Model Instances

A Model.List is an observable array of model instances. When you define a Model like Contact, a Model.List for that type of Model is automatically created. We can extend this created Model.List to add helper functions that operate on a list of model instances.

Contact.List will need two helper functions to filter a list of contacts and report how many contacts are in each category. Add this to contacts.js immediately after the Contact model:

filter() loops through each contact in the list and returns a new Contact.List of contacts within a category. this.attr('length') is included here so EJS will setup live binding when we use this helper in a view.

count() returns the number of contacts in a category using the filter() helper function. Because of this.attr('length') in filter(), EJS will setup live binding when we use this helper in a view.

If you'll be using a helper in EJS, use attr() on a list or instance property to setup live binding.

Filtering Contacts

Next, you'll modify the contactsList.ejs view to filter contacts based on the category property in the hash. In the contactsList.ejs view, change the parameter passed to the list() helper to contacts.filter(can.route.attr('category')). Your EJS file should look like this when you're done:

On line two, filter() is called with the current category from can.route. Since you used attr() in filter() and on can.route, EJS will setup live binding to re-render your UI when either of these change.

By now it should be clear how powerful live binding is. With a slight tweak to your view, the UI of the app will now be completely in sync with not only the list of contacts, but with the category property defined in the route as well.

Displaying Categories

Contacts are filtered when the category property in the hash is changed. Now you need a way to list all available categories and change the hash.

First, create a new View to display a list of categories. Save this code as filterView.ejs in your views folder:

Each link has a data-category attribute that will be pulled into jQuery's data object. Later, this value can be accessed using .data('category') on the <a> tag. The category's name and number of contacts will be used as the link test. Live binding is setup on the number of contacts because count() calls filter() which contains this.attr('length').

Listening to Events With can.Control

Control automatically binds methods that look like event handlers when an instance is created. The first part of the event handler is the selector and the second part is the event you want to listen to. The selector can be any valid CSS selector and the event can be any DOM event or custom event. So a function like 'a click' will listen to a click on any <a> tag within the control's element.

Control uses event delegation, so you don't have to worry about rebinding event handlers when the DOM changes.

Displaying Categories

Create the Control that will manage categories by adding this code to contacts.js right after the Contacts Control: