Advertisement

Diving into CanJS: Part 3

by

This is the final part of a three part tutorial 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 two, you created the Views and Controls needed to display categories, created Model.List helpers, used routing to filter contacts and modified your EJS to take advantage of live binding.

In this part of the tutorial, you will:

  • Edit and delete contacts using the Contact Control
  • Create a Control and View to create contacts
  • Listen to DOM and Model events using Control's templated event handlers

You'll be adding to the source files from part one and two so if you haven't done so already, catch up by reading part one and two.


Updating a Contact

In part one, contactView.ejs placed each property of a contact is in an input tag. To update a contact when these inputs change, you'll have to add some event handlers to the Contact Control. Add this code contacts.js inside the Contacts Control:

'.contact input focusout': function(el, ev) {
  this.updateContact(el);
},
'.contact input keyup': function(el, ev) {
  if(ev.keyCode == 13){
    el.trigger('blur')
  }
},
'.contact select change': function(el, ev) {
  this.updateContact(el)
},
updateContact: function(el){
  var contact = el.closest('.contact').data('contact');
  contact.attr(el.attr('name'), el.val()).save();
}

Let's go through this code line-by-line and see how it works:

'.contact input focusout': function(el, ev) {
  this.updateContact(el);
},

Calls updateContact() when any <input> loses focus.

'.contact input keyup': function(el, ev) {
  if(ev.keyCode == 13){
    el.trigger('blur')
  }
}

Triggers the blur event on an <input> if the enter key is pressed while it has focus. This will cause the input to lose focus, which is handled by the focusout event handler.

'.contact select change': function(el, ev) {
  this.updateContact(el)
},

Calls updateContact() when the value of the <select> changes.

var contact = el.closest('.contact').data('contact');

Finds the closest <li> parent tag and retrieves the model instance using $.data().

contact.attr(el.attr('name'), el.val()).save();

Updates the contact using attr(). The name of each <input> matches a property of contact, so el.attr('name') will return the name of the property that is being updated. save() is used to save the change to the Contact Model.


Deleting a Contact

There is a small link with an 'X' in the top right corner of each contact. When this is clicked, the contact should be deleted. To do this, add another event handler to the Contacts control that looks like this:

'.remove click': function(el, ev){
  el.closest('.contact').data('contact').destroy();
}

When the X is clicked, the contact instance is retrieved from the nearest <li> and destroy() is called. destroy() deletes the contact from the Model and removes it from any Model.Lists.

Live binding will automatically update your UI when a contact is deleted.

Creating a Contact

Now you'll create the Control and View needed to create a contact. First you'll need a giant "New Contact" button. Add this code to index.html right above <div id="filter">:

<a class="btn btn-large btn-primary" href="javascript://" id="new-contact">
  <i class="icon-plus icon-white"></i> New Contact
</a>

You'll also need to create a new View that will render a form for creating a contact. Save this code as createView.ejs in your views folder:

<div class="hero-unit contact span8">   
  <%== can.view.render('views/contactView.ejs', {
    contact: contact, categories: categories
  }) %>    
  <div class="row">     
    <div class="buttons pull-right">        
      <a href="javascript://" class="btn btn-primary save">Save</a>       
      <a href="javascript://" class="btn cancel">Cancel</a>     
    </div>    
  </div>  
</div>

This View renders the contactView.ejs sub-template and adds "Save" and "Cancel" buttons. Here's what it looks like in the application:

Create View

Now you'll need to create a new Control named Create that will display the form and save the new contact to the Contact Model. Add this code to contacts.js:

Create = can.Control({
  show: function(){
    this.contact = new Contact();
    this.element.html(can.view('views/createView.ejs', {
      contact: this.contact,
      categories: this.options.categories
    }));
    this.element.slideDown(200);
  },
  hide: function(){
    this.element.slideUp(200);
  },
  '.contact input keyup': function(el, ev) {
    if(ev.keyCode == 13){
      this.createContact(el);
    }
  },
  '.save click' : function(el){
    this.createContact(el)
  },
  '.cancel click' : function(){
    this.hide();
  },
  createContact: function() {
    var form = this.element.find('form'); 
      values = can.deparam(form.serialize());

    if(values.name !== "") {
      this.contact.attr(values).save();
      this.hide();
    }
  }
});

Let's go over this Control in detail to see what's going on:

show: function(){
  this.contact = new Contact();
  this.element.html(can.view('views/createView.ejs', {
    contact: this.contact,
    categories: this.options.categories
  }));
  this.element.slideDown(200);
},

Creates a empty contact using new Contact({}) and assigns it to this.contact. The new contact is passed to can.view() along with the categories to be rendered.

hide: function(){
  this.element.slideUp(200);
},

Slides the form up out of view.

'.contact input keyup': function(el, ev) {
  if(ev.keyCode == 13){
    this.createContact(el);
  }
}

Calls createContact() if the enter key is pressed while in one of the inputs.

'.save click' : function(el){
  this.createContact(el)
},

Call createContact() when the "Save" button is clicked.

'.cancel click' : function(){
  this.hide();
},

Calls hide() when the "Cancel" button is clicked.

var form = this.element.find('form'); 
  values = can.deparam(form.serialize());

Finds the <form> element and uses jQuery's serialize() function to get a string representing all the form's values. Then the serialized string is converted to an object using can.deparam().

if(values.name !== "") {
  this.contact.attr(values).save();
  this.hide();
}

If the name of the contact is not empty, attr() is used to update the contact stored in this.contact. save() is called to save the changes to the model and the form is hidden by calling hide().


Using Templated Event Handlers

Controls also support templated event handlers that allow you to customize an event handler and listen to events on objects other than this.element.

You customize the handler behavior using {NAME} in the event handler. The variable inside the curly braces is looked up on the Control's this.options first, and then the window. You could create multiple instances of the same Control but customize the behavior of its event handlers in each instance.

Controls can also bind to objects other than this.element using templated event handlers. If the variable inside {NAME} is an object, Control will bind to that object to listen for events. The object does not have to be a DOM element, it can be any object like a Model. To listen to a click anywhere on a page you would use: '{document} click'. as your event handler.

These handlers will get cleaned up when the Control instance is destroyed. This is critical for avoiding memory leaks that are common in JavaScript applications.

Showing the Form

You'll need to use a templated event handler to show the form when the "New Contact" button is clicked. Add this event handler to the Create Control in contacts.js:

'{document} #new-contact click': function(){
  this.show();
}

The "New Contact" button is outside of the Create Control's element, so '{document} #new-contact' is used as the selector for the button. When it is clicked, the form will slide down into view.


Initializing the Create Control

Just like the other Controls in you application, you'll need to create a new instance of the Create Control. Update your document ready function in contacts.js to look like this:

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

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

With this change, an instance of the Create Control will be created on the #create element. It will be passed the list of categories.


Reacting to a New Contact

When a new contact is created, the Model.List stored in the Contacts Control needs to be updated. You do this using templated event handlers. Add this event handler to the Contacts Control in contacts.js:

'{Contact} created' : function(list, ev, contact){
  this.options.contacts.push(contact);
}

This binds to the created event of the Contact Model. The new contact is added to the Model.List stored in the Contacts Control using push().

Live binding will update your applications UI automatically when the contact is added to this.options.contacts.

Wrapping Up

That's all for the final part of this tutorial. In part three you:

  • Used event handlers in a Control to create a new contact
  • Created a View that that render a create form
  • Used templated event handlers in a Control to bind to objects other than the Control's element

This is the end of the CanJS contacts manager tutorial. Here's a summary of what was covered in this three part tutorial:

  • Creating Controls to manage application logic
  • Render parts of an application with Views
  • Representing an application's data layer using Models
  • Simulating a REST service with fixtures
  • Using live binding to keep an application's UI in sync with its data layer
  • Listening to events with Control's event handlers
  • Working with lists of model instances using Model.List

You now have everything you need to build JavaScript applications using CanJS. Go build something awesome.

For complete documentation and more example apps, visit CanJS. Thanks for reading!

Advertisement