The Backbone.js Todo List Refactored - Part 2: Being Reactionary

The Code is here

Update : I refactored out the “trigger” call below based on some feedback by Johnny Oshika. I left the trigger part in here, however, as I think it’s a neat concept. If you have a look at the source you’ll see that you can bind the Collection to a “change” event - I did that and lost more code. Thanks Johnny!

The List

Let’s jump right in - this is going to be another long post. So far we have a Model, a Collection, a FormView and a Router to make them all dance. Let’s add the next form and get a bit more into the details of how Backbone works.

Here’s our app, once again:

I need to add a View that shows a list of items, with checkboxes that change the CSS when selected and also have an inline edit capability (the purple box, above). Somewhat of a tall order.

Starting out, it’s tempting to create a single View here, using a single template with jQuery template’s “{{each}}” function. We could then, in our view, wire the checkbox and all’s well.

But this isn’t very “Backbone-y” - let’s see why.

Think Like a Desktop App

If we put on our Desktop App Dev’s hat - it becomes obvious rather quickly that we wouldn’t have just one View for this entire List. We would have at least 2: the List as a container, and the ListItem for handling item events and view “stuff”.

Let’s start with the ListView:

The first method here is the most important - and I’ll be talking more about this later in the post - because it shows the “style” in which I’m working with Backbone:
my Views are “reactive”.

This View will be handed a collection when it’s instantiated (we saw this yesterday) as well as an “el” in which to render itself.

Once our ListView here has its Collection - it “binds” to the Collection whenever an item is added or removed. What does it do then?
It renders itself.

This means that if I ever add an item to the Collection anywhere on the page, or take one away, this ListView will update itself. Pretty damn handy.

Let’s take a look at “render”. The first line is self-explanatory: it clears out all existing DOM elements inside the ListView’s “el” - or “core DOM element”. Next, we setup an array to hold a set of “sub DOM elements” because we’re going to loop the passed-in collection and do something fun.

Which is the very next line. We loop over each item in the Collection - which is an individual Model - and we send that Model into what we can think of as a “Child View” - the ListItemView (which I’ll talk about in a second).

We pull out the rendered DOM element from the Child View and pop it into the “el”. Note that we can just add it to the “el” of our ListView using jQuery’s “append” - but
Dave Ward suggested to me that if we have a big list - this can be a performance issue. So I believe him… he’s Dave Ward.

Finally - we append the “els” array to the “el” of our ListView - which renders the content directly to the page.

The ListItemView

You might be thinking - “isn’t this a bit complicated? Do you really need a Parent/Child view here? Why don’t you let the template run the iteration?”.

As I mention, I did just that originally - but I ran into a problem when it came to properly selecting a “row”. By using a “Child View” approach - this becomes all too easy.

Here’s the code:

The first line is something we haven’t seen: “tagName”. You can tell a View that when it renders - if the “el” wasn’t set explicitly as I’ve done with the ListView - to make it a certain tag. This is handy if you’re using a table and want to render out a “tr” or something. In this case - I want everything rendered from this ListItemView to be rendered into an “li” element.

Next - our model bindings.
We want our ListItemView to “react” whenever certain changes happen on our Model. Jumping to the bottom of “initialize” - whenever the Model’s status changes - we want the View here to call the “setStatus” method. Same with “name” - I’ll get to these methods in a minute.

The line above is one that drives me crazy as I never remember to do it until it’s too late. When events go off on a Model or a Collection - “this” gets rescoped (as near as I can tell). So if you use “this” inside your event handlers - it won’t be referring to your View anymore, and you’ll get an error.

Underscore’s “_.bindAll(this, …)” method solves this. You tell it the methods you’re using as event handlers, and it will rescope “this” to be the current View.

Next up - the “events” hash. Here, once again, we’re binding events on our View (as opposed to our Model, which we did in “initialize”). We’re concerned with quite a few things here: the click of a checkbox, the click of a span with the class “destroy” - etc. Hopefully by now you can see the pattern:

“event $(selector)” : “methodOnView”

The render method is very, very basic. The template is rendered using the passed-in Model as data, popped into “el”, and returned.

The rest of the functions manipulate elements on the View itself. This is where the power of Backbone starts to shine (for me, at least). KnockoutJS would have you pop this into the “data-bind” attribute on a given DOM element - sometimes chaining 2 or 3 method calls in that you have to specify on the ViewModel.

I like this approach better - they’re functions on the VIEW, not the Model (which doesn’t make sense). Moreover it’s clean - separate from my template and I can fix problems in one place.

toggleComplete() simply tells the Model to set its status to “incomplete” or “completed” - depending on the current status.

clear() removes the bound model from the “tasks” Collection, which is a global variable. Here I could have said “this.model.collection.remove(…)” but I think that’s not as clear as just calling on the global.

setStatus() updates the class on the “row”. This is where I was having issues before - trying to get the jQuery selector syntax right for selecting the DIV which contained the selected checkbox. I could have, using “closest” - but that’s silly. Here, in this code, is a neat feature of Backbone: this is scoped to the current “el” so I can select the jQuery element from inside “el”. 

Not only does this read better - it’s cleaner and makes a whole lot more sense.

toggleEdit() does sort of the same thing - changing the outer class to “editing” which turns off the “li” and turns on the input tag.

Finally - “
updateModel() ” - this turns “off” the editor and sets the model’s value (which also triggers it’s “change” event. This is called  onBlur, so editing should be complete. I could also wrap this in a form tag if I like to capture the “submit” - but this seems to work fine.

All of these functions are simple, one or two-line bits of instruction that are called as reactions to events on the DOM.

Let’s see more of this type of thing with the StatsView.

The StatsView

The final view that we need to wire up is the StatsView, which is in yellow, above. This view does a bit too much for a single view - but it’s pretty straightforward so we’ll keep it simple for now.

The idea is that you want to show the user how many tasks they have remaining, and then give them the ability to remove the completed one. There’s some math involved here - as well as some admin functionality.

First, let’s create the template using jQuery templating:

We have two bits of data we need to throw in - the number of {remaining} tasks as well as the number of {completed}.

To do this, we can flex two methods on the Collection that we created earlier: “completed()” and “incomplete()”:

These methods use the Underscore library’s helper method, “select”. Hopefully it’s easy enough to understand.

What I want to do here is simply get the count, and push it into the template as data. So I’ll create a StatsView and pass in the collection as a whole - I’ll want to bind to events on that collection so I can update the stats:

Starting from the top: by now you should be used to seeing “initialize” and what goes on there: 

*The setting of the template

*Rescoping “this” as-needed

*Binding to Model or Collection eventsThe event-binding is the interesting thing here - we’ve seen add and remove, but what’s “status-changed”?

The answer is that
it’s an arbitrary “trigger” that I’m calling somewhere else in the app. If you had to - can you guess when and where I’d call that trigger? We’ll see that in a moment….

In the events hash for the View, I’m wiring up that link so that it will clear out the completed tasks.

In “render” I’m using the “completed()” and “incomplete()” methods on the Collection to give me correct counts, then I’m passing them into the template as a JSON chunk. Finally - the StatsView is rendering itself to a passed-in “el”.

clearCompleted() is the final function, and one that makes me happy. It’s one thing to write Ruby that is self-documenting, another to do that with javascript. I think it’s pretty obvious what’s happening here.

This entire View is instrumented based on reactions to the underlying Collection. Whenever an element is added, removed, or has its status change. In fact the StatsView isn’t even rendered when the document loads up - it only renders when there’s data! 

Now if this was a real app, calling out to a database to initially load data in - you would want to be sure to bind to the “fetch” event here as well. Same effect.

So let’s get back to that “status-changed” thing. Where is that? It’s on the Model:

Inside the ListItemView, the “click :checkbox” fires a “toggleComplete” method, which calls “this.model.toggleStatus()” on the underlying Model. That’s the method you see here.

This method on the Model sets the status as-needed, then tells its collection that its status has changed. It doesn’t care who is listening - its only job is to run the notification. There’s no code on the Collection to worry about - only the trigger itself.

Which is pretty damn powerful.

Summary

Backbone isn’t easy - but if you play around with it for a bit and think of it in terms of desktop programming - suddenly things start making a whole lot more sense.

As I mention, I’m absolutely certain that I can clean this up and do things a better way - the truth is I’m pretty new at all of it and I figure that if you care - maybe you’ll fork the repo and help me out.