# namespace JSON under 'todo' see backbone_sync.js
paramRoot: 'todo'
# Build the url, appending _id if it exists:
url: ->
u = "/api/todos"
u += "/#{@get("_id")}" unless @isNew()
return u
# The default Backbone isNew function looks for 'id',
# Mongoose returns "_id", so we need to update it accordingly:
isNew: ->
!@get("_id")?
When writing a Backbone model, it is important that we extend the Backbone.Model class;
otherwise, we won’t get any of the functionality we expect of a Backbone-based model.
Because we are using backbone_sync.js, we need to set the name we want our data to be
nested under when it is sent back to the server. We do that by setting paramRoot to todo.
Next, we need to tell Backbone what URL this model will use to talk to the API. We do this
by creating a url function. Backbone will automatically look for this function later and tell
it where our API is located. When we have a new Todo object, it won’t have an ID associated
with it, so we want to append it only if the object isn’t new. The isNew function built in to
Backbone will return true or false based on whether the object is “new” or not.
Tip
If you want to retrieve an attribute on a Backbone object, such as the title or _id attribute,
you have to use the get function to do that. That is because all attributes on a Backbone
model are stored in a variable called attributes to prevent any sort of clashing between the
Backbone attributes and functions and your attributes.
The isNew function in Backbone does its magic by looking to see if the object has an id attribute. If it does, it considers the object not to be new. Unfortunately, MongoDB8 does not
www.it-ebooks.info
Writing our Todo Model and Collection
return an id attribute, but rather an _id attribute. Because of this, we need to rewrite the
isNew function to behave the way we want it to.
Tip
Like the isNew function, we had to write a custom url function because MongoDB uses _id
instead of id. If we had an _id attribute, we could have set the url attribute (not function)
equal to /api/todos in the Todo model, and Backbone would have automatically appended
the id attribute to the url attribute for us. But, as it is, here we are.
With the Todo model written, let’s write the associated collection, Todos. A collection, as we
mentioned earlier, is a list, similar to an array, that holds many Todo models. In this application we will use the Todos initially to fetch all the existing todos from our API.
Tip
Personally, I find it a little annoying that I have to have a separate class to manage a collection
of models, but it’s a very small price to pay for the features and functionality you get from this.
It’s just something you learn to live with. As a side note, I usually place the collection class
definition in the same file as the model definition. It makes it easier to find and change later.
In this case, I have them separated because it makes it easier to show you the code.
The Todos collection class is going to be really simple:
When the DOM is loaded, we will create a new instance of the template, so we can use it to
render each todo out after we fetch them.
Next, we create a new instance of the Todos collection and assign it to a variable named todos.
With an instance of the Todos collection ready, we can call the fetch function. The fetch
function will use the url attribute we set on the Todos collection to talk back to the server and
fetch a list of todos for us. If the fetch is successful, it will call the success callback we passed
into the fetch function.
The success callback, if executed, will call the forEach function on the todos object to iterate
over the list of Todo models it retrieved from the server. We then render the template using
each todo and write them to the screen.
The result of all this is that you should see your existing todos nicely printed to the screen
when you reload your application. Don’t expect to be able to update or destroy the todos yet.
We’ll get to that later. In the next section, we are going to write our first Backbone view to
replace that display code we just wrote.
www.it-ebooks.info
Listing Todos Using a View
Listing Todos Using a View
The previous code we wrote works, but it can definitely be made a lot cleaner and more flexible. That’s where Backbone.View classes come in. Let’s replace what we’ve already written
with a Backbone.View class to clean it up.
First create a views folder under the assets folder. That is where all the view files will live. In
that file, let’s create todo_list_view.coffee and fill it with the following:
So what is going on with this code? Great question. The first thing we do is create a new class,
TodoListView, and have it extend Backbone.View. By extending the Backbone.View class we
get access to some helpful functions and features that we’ll be using throughout the rest of this
chapter.
Tip
Notice that, like the Todo and Todos classes, we are defining the TodoListView class with
a prefixed @ symbol. The @ will make sure the classes are available outside of the automatic
wrapper function CoffeeScript writes around each .coffee file. If we didn’t do this, we wouldn’t
have access to these classes outside of their respective .coffee files.
Next, we tell the view that the element on the page we want to associate this view with is the
#todos element. We do this by setting the el attribute. If we didn’t do this, Backbone would
create a new div object for the el attribute, and you would be responsible for placing that
element on the page yourself. We will see this in action in a little bit.
We move on next to the initialize function. The initialize function is a special function that will be called by Backbone after an instance of the view object has been initialized.
You definitely do not want to write a constructor function in your view classes. This can
www.it-ebooks.info
263
264
Chapter 12
Example: Todo List Part 3 (Client-side w/ Backbone.js)
potentially override all the rich chocolaty goodness that Backbone is trying to create for you.
If you need to have things happen when the view is instantiated, the initialize function is
definitely the way to go.
As it happens, we have a few things we do want to do when the TodoListView class is instantiated. In particular, we have a few things we want to do with the @collection object. Your
first question should be, Where did that variable come from? Backbone has a few “magic”
variables and attributes and @collection and @model are two of them. In a minute we will see
that when we create an instance of the TodoListView class, we are going to pass it an object
that contains a collection key that has a value of new Todos(). That will then get assigned
to the @collection object, in the TodoListView, giving us access to the Todos collection.
When we look at the necessary changes to the application.coffee file shortly, this should
all become a bit clearer.
What do we need to do with the @collection object, also known as a Todos collection? First,
we are going to call the bind function and tell it that whenever the collection triggers a reset
event, we want to call the @render function in the TodoListView instance we have.
How does a collection object trigger a reset event? One of the ways, and probably the most
common in Backbone, is through the fetch function. When called, the fetch function will
get the full list of todos from the API, as we saw earlier. Because we need those todos, we call
the fetch function as the last line of the initialize function. The calling of the fetch function will, in turn, trigger a reset event, which will then call the @render function.
The @render function is where we will print out the list of todos in the collection to the page.
The @render function shouldn’t look too different from the original code we had in application.coffee to render each todo on the screen. The big differences are that we are calling the
forEach function directly on the @collection object, instead of through a success callback.
The other difference is that we no longer have to refer to the #todos element directly; instead,
we can use @el which will point there for us. Using @el instead of the name of the element
directly is great should we ever have to refactor our code. We just change the value of @el and
don’t have to change the rest of the code base.
Tip
The @render function is declared using the => syntax instead of the -> so that when it is
called after the reset event has been triggered, the @render function knows its context and
has access to the rest of the class. If a -> was used, this code would result in an
error similar to TypeError: 'undefined' is not an object (evaluating 'this.
collection.forEach') because @render would not have access to the @collection
object. If we really wanted to use the -> syntax, we would have to have manually bound
the function ourselves in the initialize function by using the bindAll function in the
Underscore library. _.bindAll(@, "render"). I would rather just use the => syntax.
All that is left now is to clean up the application.coffee file to use the new TodoListView
Example: Todo List Part 3 (Client-side w/ Backbone.js)
saveModel: (e) =>
e?.preventDefault()
model = new Todo()
model.save {title: @$('.todo_title').val()},
success: =>
@collection.add(model)
error: (model, error) =>
if error.responseText?
error = JSON.parse(error.responseText)
alert error.message
It’s a bit longer than the TodoListView class we just wrote, but most of that is the saveModel
function, which, by now, should be pretty old hat to you. However, we’ll discuss it briefly in
just a second.
The TodoListView needs to associate itself with the #new_todo element on the page, so again,
we can set this via the el attribute.
Next, we have to tell the NewTodoView class to listen for certain events and respond to those
events when they happen. Backbone lets us easily map those using the events object attribute.
Mapping events using the events attributes is a little weird, though. The key for the event
you want to create is a compound key. The first part is the event you are waiting for, click,
submit, keypress, and so on, that is then followed by the CSS selector you want to watch for
the event. The value of the mapping is the function you want to call when that event on that
CSS selector happens. In our case, we are watching for a keypress event on the .todo_title,
and when that happens, we want to call the handleKeypress function.
Tip
There are two important things to note about the events mapping in Backbone. The first is
that the CSS selector is scoped to the el attribute you set. The second is that we pass in a
string with the function name, not a reference to the function, like we do when binding to collections, as we saw earlier. I’m not sure about the reason for this mismatch, but that’s just the
way it is. This is something to look for if things aren’t working quite as expected.
In the initialize function we want to bind the resetForm function to the add event on the
@collection object, which we will pass in when we create the instance of the NewTodoView
class. Later in the saveModel function, when we get an acknowledgement from the API that we
have successfully created the new todo, we will add it to the @collection. That will trigger the
add event, which will call the resetForm function. The resetForm function, as you can see,
cleans up the form to its original state before the user typed in the todo.
Also in the initialize function, we want to set the .todo_title element in the form to
have focus when the page loads. Here we can use a special function on the Backbone.View
class, @$. The @$ function lets us write jQuery CSS selectors that are already scoped to the @el
element we are watching in the view. Without this special function, we would have to write
something like $('#new_todo .todo_title') to get access to the same element.