Advertisement
JavaScript & AJAX

Diving into CanJS: Part 2

by

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:

Contact.List = can.Model.List({
  filter: function(category){
    this.attr('length');
    var contacts = new Contact.List([]);
    this.each(function(contact, i){
      if(category === 'all' || category === contact.attr('category')) {
        contacts.push(contact)
      }
    })
    return contacts;
  },
  count: function(category) {
    return this.filter(category).length;
  }
});

The two helper functions here are:

  • 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:

<ul class="unstyled clearfix">
  <% list(contacts.filter(can.route.attr('category')), function(contact){ %>
    <li class="contact span8" <%= (el)-> el.data('contact', contact) %>>
      <div class="">
        <%== can.view.render('contactView', {contact: contact, categories: categories}) %>
      </div>
    </li>
  <% }) %>
</ul>

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:

<ul class="nav nav-list">
  <li class="nav-header">Categories</li>
  <li>
    <a href="javascript://" data-category="all">All (<%= contacts.count('all') %>)</a>
  </li>
  <% $.each(categories, function(i, category){ %>
    <li>
      <a href="javascript://" data-category="<%= category.data %>"><%= category.name %> (<%= contacts.count(category.data) %>)</a>
    </li>
  <% }) %>
</ul>

Let's go over a few lines from this code and see what they do:

<% $.each(categories, function(i, category){ %>

$.each loops through the categories and executes a callback for each one.

<a href="javascript://" data-category="<%= category.data %>"><%= category.name %> (<%= contacts.count(category.data) %>

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:

Filter = can.Control({
  init: function(){
    var category = can.route.attr('category') || "all";
    this.element.html(can.view('filterView', {
      contacts: this.options.contacts,
      categories: this.options.categories
    }));
    this.element.find('[data-category="' + category + '"]').parent().addClass('active');
  },
  '[data-category] click': function(el, ev) {
    this.element.find('[data-category]').parent().removeClass('active');
    el.parent().addClass('active');
    can.route.attr('category', el.data('category'));
  }
});

Let's examine the code from the `Filter` Control you just created:

this.element.html(can.view('filterView', {
  contacts: this.options.contacts,
  categories: this.options.categories
}));

Like in the Contacts Control, init() uses can.view() to render categories and html() to insert it in to the Control's element.

this.element.find('[data-category="' + category + '"]').parent().addClass('active');

Finds the link that corresponds to the current category and adds a class of 'active' to its parent element.

'[data-category] click': function(el, ev) {

Listens for a click event on any element matching the selector [data-category].

this.element.find('[data-category]').parent().removeClass('active');
el.parent().addClass('active');

Removes the 'active' class from all links then adds a class of 'active' to the link that was clicked.

can.route.attr('category', el.data('category'));

Updates the category property in can.route using the value from jQuery's data object for the <a> that was clicked.


Initializing the Filter Control

Just like the Contacts Control in part one, you need to create a new instance of the Filter Control. Update your document ready function to look like this:

$(document).ready(function(){
  $.when(Category.findAll(), Contact.findAll()).then(function(categoryResponse, contactResponse){
    var categories = categoryResponse[0], 
      contacts = contactResponse[0];

    new Contacts('#contacts', {
      contacts: contacts,
      categories: categories
    });
    new Filter('#filter', {
      contacts: contacts,
      categories: categories
    });
  });
})

With this change, an instance of the Filter Control will be created on the #filter element. It will be passed the list of contacts and categories.

Now, when you run your application in a browser, you will be able to filter contacts by clicking on the categories on the right:

Part 2

Wrapping Up

That's all for part two! Here's what we've accomplished:

  • Created a Control that listens to events and manages categories
  • Setup routing to filter contacts by category
  • Tweaked your views so live binding will keep your entire UI in sync with your data layer

In part three, you'll update your existing Controls to allow contacts to be edited and deleted. You'll also create a new Control and View to that enables you to add new contacts.

Can't wait to learn more? Part three of the series has been posted here!

Related Posts
  • Code
    Android SDK
    Create a Music Player on Android: Song Playback0d63m preview image@2x
    In this series, we are creating a music player on Android using the MediaPlayer and MediaController classes. In the first part, we created the app and prepared the user interface for playback. We presented the list of songs on the user device and specified a method to execute when the user makes a selection. In this part of the series, we will implement a Service class to execute music playback continuously, even when the user is not directly interacting with the application.Read More…
  • Code
    Theme Development
    Custom Controls in the Theme CustomizerTheme customizer custom control 400
    In the last article, we explored the advanced controls available in the Theme Customizer, and how to implement them. We’re going to look at how to create our own custom control, allowing you to choose which Category of Posts are displayed on the home page. To get started, download version 0.6.0 of our Theme Customizer Example.Read More…
  • Code
    HTML5
    HTML5: Battery Status APIPdl54 preview image@2x
    The number of people browsing the web using mobile devices grows every day. It's therefore important to optimize websites and web applications to accommodate mobile visitors. The W3C (World Wide Web Consortium) is well aware of this trend and has introduced a number of APIs that help with this challenge. In this article, I will introduce you to one of these APIs, the Battery Status API.Read More…
  • Web Design
    UX
    Walk Users Through Your Website With Bootstrap TourTour retina
    When you have a web application which requires some getting used to from your users, a walkthrough of the interface is in order. Creating a walkthrough directly on top of the interface makes things very clear, so that's what we're going to build, using Bootstrap Tour.Read More…
  • Code
    JavaScript & AJAX
    Ember Components: A Deep DiveEmber components retina preview
    Ember.js is a JavaScript MVC framework that allows developers to create ambitious web applications. Although pure MVC allows a developer to separate concerns, it does not provide you with all the tools and your application will need other constructs. Today, I'm going to talk about one of those constructs. Ember components are essentially sandboxed re-usable chunks of UI. If you are not familiar with Ember, please check out Getting Started With Ember.js or the Let's Learn Ember Course. In this tutorial, we will cover the Web Components specification, learn how to write a component in Ember, talk about composition, explain the difference between an Ember view and an Ember component, and practice integrating plugins with Ember components.Read More…
  • Code
    JavaScript & AJAX
    Integrating a JS Build Process Into MSBuild in Visual Studio 2012 ExpressMsbuild retina preview
    I've been working with ASP and ASP.NET for about ten years now, starting with ASP classic and settling on .NET 2.0 as my favorite. My new year resolution this year (2013) was to upgrade my .NET work to .NET 4.0 using Visual Studio 2012 Express and really get to grips with MSBuild, so that I can concatenate and minify my JavaScript files as part of the normal build process of a .NET project, in Visual Studio. My first love is to use Ant in NetBeans with a PHP or JSP platform for this kind of work, but my company's main website runs on a .NET platform and it's time to update it, so I decided to bite the bullet and dive back in to some serious study of creating a fully integrated build process using MSBuild. This tutorial will show you how to edit your Visual Studio 2012 Express project file to include your own separate build file which will perform the now widely familiar process of concatenating and minifying a set of JavaScript modules into one file ready for deployment. Read More…