Loading...
Programming Lessonsswigs

Let’s Build A Todo App With BackboneJS And CoffeeScript

todos

This tutorial will teach you how to put together a Todo app using CoffeeScript and BackboneJS. You can find the entire code for this tutorial on the Github repository.

Here’s What We’ll Build

The todo app is inspired by TodoMVC, except that we’re using Backbone.js alongside CoffeeScript as our stack, and we’ll style the app differently.

todos

It should allow a user to specify items to be done later, and to check off those items as they complete them. In addition, a user can filter items by status so they can see just the completed items or the non-completed ones. Here’s another screenshot of the application.

the app

Funny story about CoffeeScript and BackboneJS, they have something in common.

Quick question: What do they have in common. This is a really geeky-culture test, so don’t feel bad if you could only name JavaScript as being the unifying theme.

No, beyond that, the core unifier is that both of these popular open source tools were developed by the insanely prolific and insightful Jeremy Ashkenas, who, along with TJ and a handful others, has gotta be one of the best things to happen to the JavaScript community.

So if you’ve coded for any reasonable time with Backbone or Coffeescript, you can’t help but notice the same examples of delightful craftsmanship and detail that Jeremy has brought to both projects. He has worked on UnderscoreJS and other things, and I’m an avid user of Underscore as well. So if you haven’t come across that great library before, you should certainly check it out. With little doubt, using Underscore, and reading the source, will make you a much better JavaScript developer.

Getting Backbone.js And Coffeescript

First order of business, download Backbone.js here. You’ll want to follow the instructions as far as downloading Backbone’s dependencies as well, which should be just Underscore and jQuery. This tutorial is based on version 1.3.3 of Backbone.js, but should work with newer versions. Given the stability of Backbone, even older versions of the library will work with this example code with minimal modifications. Another great benefit of working with Backbone.

For CoffeeScript, the recommended way is to install the command line compiler and REPL via npm. Install it using the installation instructions found here.

Now create a new directory called “BackboneTodos” anywhere on your computer. Inside this directory, create a new directory called js, which is where our JavaScript and CoffeeScript files are going to live.

Next, inside your js directory, create a new directory called lib where you will place all of Backbone.js and its dependencies, excluding CoffeeScript which has its own usage and installation as detailed above.

Assuming you have CoffeeScript, Backbone.js and dependencies installed, there remains one more dependency. If you scan my source code, you’ll see I’m using the Backbone.LocalStorage plugin to store the Todos. You should grab a copy of this file and save it, again, in your lib folder.

So currently, your folder structure should look as follows

BackboneTodos/js/lib

Note, there is nothing special about the “lib” folder, it’s just an arbitrary name for a folder where we are holding our application’s libraries.

Say Hello World

Now let’s create our home page. Create a new file inside your BackboneTodos directory, which is the root folder of the app. Call the new file, predictably, index.html

Inside index.html, let’s create the basic HTML template that says “Hello World” so we know we’re in business. Here’s the code you should put in index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Zeyron Task Manager</title>
    <link rel="stylesheet" href="css/index.css">
    <link rel="stylesheet" href="css/blingstyles.css">

    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js"></script>
      <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
     <h1>Hello World!</h1>
  </body>

You’ll notice that I’m including some shims and meta tags. Copy everything in. This is just a standard boilerplate that sets up the page to support different browsers.

Now open index.html in your web browser and you should see the text “Hello World”.

backbone.js

Say It With Style

If you look carefully at the above code, you’ll notice I’m including two style sheets. The first has some basic styles, and the second really brings on the style, so you will want to grab one or both stylesheets in order to get your UI looking neat. Place them in the appropriately named css folder, so now your file structure nests a js folder and a css folder under the BackboneTodos parent folder.

In addition to the CSS files, now let’s load Backbone.js and all its dependencies on the page. Before the closing </body> tag in your index.html, now add some script tags to load these javascript libraries, as follows.

<script src="js/lib/jquery.js"></script>
    <script src="js/lib/underscore.js"></script>
    <script src="js/lib/backbone.js"></script>
    <script src="js/lib/backbone.localStorage.js"></script>

We still don’t have the actual todos shown on the page, so let’s add some HTML that will consist of a section where our Todos will be listed. We will also remove the line that reads <h1>Hello World!</h1>, we don’t need it now. Instead, we will replace it with the following HTML.

<section class="responsive">
      <h1>BackboneJS-CoffeeScript Todos</h1>
      <section id="todoapp">
        <header>

        </header>
        <input id="new-todo" placeholder="What needs to be done?">
        <section id="main">
          <input type="checkbox" id="toggle-all" hidden>
          <label for="toggle-all">Mark all as complete</label>
          <ul id="todo-list">

          </ul>
        </section>
        <footer id="footer"></footer>
      </section>
      <div id="info">
        <p>Double-click to edit a todo</p>
        <p>Written by <a href="http://towersofzeyron.com">Tendai Mutunhire</a></p>
        <p>A production of <a href="http://towersofzeyron.com">TowersOfZeyron</a></p>
      </div>
    </section>

This is the main section of our app where we are going to display Todos, mark todos as completed, as well as add and remove todos. Once you’ve added the new HTML, when you do a page refresh, this is what you should see.

coffeescript

If your app doesn’t look half as good, make sure you have downloaded the two style files I linked to in the repository above “index.css” and “blingstyles.css”. Once you’ve downloaded them, remember to include them in the head section of index.html using stylesheet tags.

See that giant input field that says “What needs to be done?”. Now you should try to enter some text in there for a todo. Press enter when you are done and see what happens.

READ  (Almost) Hands-Off Rails Deployments With Mina and Nginx on Ubuntu 14 LTS

Wait, nothing happened! That’s right, for that to happen we need the help of Backbone.js, so let’s actually create some code now that will add logic to save whatever text we type as a new todo item.

Backbone Collections, Models And Views

This is the part where you learn enough Backbone.js to get your hands dirty. Backbone is built around the MVC pattern, heavily inspired by Ruby on Rails and similar frameworks. It brings MVC to the browser in a very elegant way.

Take our Todo app for instance. Backbone has the notion of a Collection, which is a special Backbone class that will allow us to store all our todos in one grouping that we can update or filter to retrieve or set new information.

Individual Todos will be represented by a Model, which is a mirror of what our data looks like on the server or in a database. Here, we are using local storage instead of a database, but frequently, you will have a database on the server that you work with.

For each Model, there will also be a view, which presents the details of the Model to the user. The View does not change the Model directly. Rather the view fires off events, which are then responsible for changing the state of the model. The view also listens for changes to the model and the collection of models. When these change, then the view changes.

Now, let us create our Todo model, which will hold details about an individual Todo item. Think about what attributes a Todo needs to have:

It obviously needs a title, telling us what is to be done, as well as a status, whether the Todo has been completed or has not yet been completed. We could add more attributes to get fancy, but let’s go with these basic attributes for now.

Create a new file at the path BackboneTodos/js/models/todo.coffee containing the following definition of the Todo model.


@Todo = Backbone.Model.extend
  defaults:
    title: ''
    completed: false

  toggle: ->
    this.save
        completed: !this.get 'completed'

That’s CoffeeScript that will be compiled into JavaScript, and defines Todo as a Backbone model with a title and “completed” status. There’s also a function toggle defined, so we can call it whenever we want to change the completed status of a given todo.

Now to compile your CoffeeScript into JavaScript, we’ll use that CoffeeScript compiler we installed earlier.

In a terminal window, cd into your project’s root folder, and run the following:

coffee -wc .

This runs CoffeeScript with the compile and watch flags, which tells CoffeeScript to compile any CoffeeScript files in the current directory, and watch for any changes. Do not forget the dot (.) at the end of that command, it stands for the current directory. Leave that terminal command running, and look inside the BackboneTodos/js/models folder, and you should see a new file, todo.js, has appeared. That’s our todo.coffee compiled into JavaScript. 

Now let’s define a Collection that will hold Todo models. Create a new file at BackboneTodos/js/collections/todos.coffee. Inside that file, include the following definition of a Collection named TodoList, and an instantiation, finally, of such a collection.


# Todo Collection
@TodoList = Backbone.Collection.extend
  model: Todo
  localStorage: new Backbone.LocalStorage 'todos-backbone'

  completed: ->
    this.filter (todo)->
      todo.get 'completed'

  remaining: ->
    this.without.apply this, this.completed()

  nextOrder: ->
    if !this.length
      return 1
    this.last().get('order') + 1

  # Todos are sorted by their original insertion order
  comparator: (todo)->
    todo.get 'order'

# create a global collection of Todos
@Todos = new TodoList()

Likewise, CoffeeScript, that’s running with the coffee -wc . invocation, will compile this new file into JavaScript on the fly. Inside the new CoffeeScript file, we are specifying the Todo model defined earlier as the model we want this new TodoList collection to hold. In other words, it will aggregate a bunch of Todo models.

Further, we are defining a completed method to filter for Todos that are complete, as well as a few other methods to allow us to query and update the list of Todos.

The next major piece we will want now is to include a router, which will allow us to filter and view specific subsets of our Todo collection. There are three filters we want to be able to see: all (shows all todos), completed (only shows completed todos) and active (only shows active todos).

So let’s create a new router file at BackboneTodos/js/router/router.coffee with the following

WorkSpace = Backbone.Router.extend
  routes:
    "*filter": "setFilter"

  setFilter: (param)->
    console.log "inside router filter"
    console.log param
    if param
      param = param.trim()
    @TodoFilter = param
    Todos.trigger "filter"

# @TodoFilter = "completed"
@TodoRouter = new WorkSpace()
Backbone.history.start()

We have a Collection, we have a Model, but where is the View?

Before we get to the View part of MVC so we can see our app in all its bling, let’s set up our index.html to utilize the new code we just added, as well as pull in some as-yet undefined scripts that will set up the View.

Back in index.html, under our script tags we defined earlier, add the following script tags, before the closing </body> tag.

<script type="text/javascript">
      window.TodoFilter = {}
</script>
<script src="js/models/todo.js"></script>
    <script src="js/collections/todos.js"></script>
<script src="js/views/todos.js"></script><script src="js/views/app.js"></script>
<script src="js/router/router.js"></script>
<script src="js/app.js"></script>

We’re pulling in the scripts we wrote earlier. You’ll also notice we’re pulling in 3 scripts that are currently missing.

In order to use these scripts, let’s first define a couple of templates, which Backbone will use to render new Todos and filter todos.

The first template defines the view for a new todo, which we will append to the main document when a user enters text for a todo. Add the following code to index.html, inside the body but before the first of the script tags.

Including the last part of the section defined earlier, it looks like the following:

<div id="info">
        <p>Double-click to edit a todo</p>
        <p>Written by <a href="http://towersofzeyron.com">Tendai Mutunhire</a></p>
        <p>A production of <a href="http://towersofzeyron.com">TowersOfZeyron</a></p>
      </div>
    </section>


    <script type="text/template" id="item-template">
      <div class="view">
        <input class="toggle" type="checkbox" <%= completed ? 'checked' : ''%>>
        <label class="todo-label"><span><%= title %></span></label>
        <button class="destroy">x</button>
        <input class="edit" value="<%= title %>" style="border-top:1px solid" hidden="hidden">
      </div>
    </script>

The template is enclosed in a script tag, but we are giving it a type of “text/template” so it does not show immediately on the page. Rather, when the user clicks enter after typing a Todo, we will then use Backbone to add a li list item, containing the HTML in the defined template. As you can see, we will render the title of the Todo, as well as a checkbox showing if it’s complete or not, and a button that allows us to destroy the Todo.

READ  The Ultimate List of 200 Greatest Web Apps On The Net: A Product Maker's Guide

We can now write the Backbone code that constructs the View for an individual todo, using the template defined above, and listens for events such as a user hitting the “enter” key. Create a new file at BackboneTodos/js/views/todos.coffee with contents as below.

`var app = app || {}`


@TodoView = Backbone.View.extend
  tagName: "li"
  template: _.template( $("#item-template").html() )
  events:
    "click .toggle": "toggleCompleted"
    "dblclick label": "edit"
    "click .destroy": "clear"
    "keypress .edit": "updateOnEnter"
    "blur .edit": "close"

  initialize: ->
    this.listenTo this.model, "change", this.render
    this.listenTo this.model, "destroy", this.remove
    this.listenTo this.model, "visible", this.toggleVisible

  render: ->
    console.log "rendering todo"
    this.$el.html this.template(this.model.attributes)
    this.$el.toggleClass("completed", this.model.get("completed"))
    this.toggleVisible()
    this.$input = this.$(".edit")
    this

  toggleVisible: ->
    this.$el.toggleClass("hidden", this.isHidden())

  # determines if item should be hidden
  isHidden: ->
    isCompleted = this.model.get "completed"
    return((!isCompleted && TodoFilter == "completed") || (isCompleted && TodoFilter == "active") )

  # toggle the "completed" state of the model, the change is immediately persisted
  toggleCompleted: ->
    this.model.toggle()

  edit: ->
    console.log "now editing"
    this.$el.addClass "editing"
    this.$input.removeAttr("hidden")
    this.$input.focus()

  close: ->
    value = this.$input.val().trim()

    if value
      this.model.save({title: value})
    else
      this.clear()

    this.$el.removeClass "editing"

  updateOnEnter: (e)->
    if e.which == ENTER_KEY
      this.close()

  clear: ->
    this.model.destroy()

Here’s the documentation about Backbone Views which you should study a bit to really see the elegance of what the above view code achieves, courtesy of Backbone. Now events and user interaction are handled for us thanks to the above.

We have a View defined for Todos, but we need to actually make an instance of the view to have all the functionality on the page. We will therefore create a main view of the application, which is what will create the individual Todo Views when a new Todo is entered in the input for new Todos. This view will also list all the existing Todo items in a list. So now create a new file at BackboneTodos/js/views/app.coffee which contains the definition of the main View.

`var app = app || {}`
# @TodoFilter = "completed"

@AppView = Backbone.View.extend
  el: "#todoapp"
  statsTemplate: _.template $("#stats-template").html()

  # delegated events for creating new items and clearing completed ones
  events:
    "keypress #new-todo": "createOnEnter"
    "click #clear-completed": "clearCompleted"
    "click #toggle-all": "toggleAllComplete"

  initialize : ->
    this.allCheckbox = this.$("#toggle-all")[0]
    this.$input = this.$("#new-todo")
    this.$footer = this.$("#footer")
    this.$main = this.$("#main")

    this.listenTo Todos, "add", this.addOne
    this.listenTo Todos, "reset", this.addAll
    this.listenTo Todos, "change:completed", this.filterOne
    this.listenTo Todos, "filter", this.filterAll
    this.listenTo Todos, "all", this.render

    # get the Todos from the store
    Todos.fetch()

  # New, re-rendering the app just means refreshing the statistics
  # The rest of the app doesn't change
  render: ->
    completed = Todos.completed().length
    remaining = Todos.remaining().length

    if Todos.length
      this.$main.show()
      this.$footer.show()
      this.$footer.html this.statsTemplate
        completed: completed
        remaining: remaining

      this.$("#filters li a")
        .removeClass "selected"
        .filter('[href="#/' + ( TodoFilter || '' ) +  '"]')
        .addClass "selected"
    else
      this.$main.hide()
      this.$footer.hide()
    this.allCheckbox.checked = !remaining

  addOne: (todo)->
    view = new TodoView({model: todo})
    console.log "adding one todo"
    this.$("#todo-list").append view.render().el

  addAll: ->
    this.$("#todo-list").html ""
    console.log "adding all todos"
    Todos.each this.addOne, this

  filterOne: (todo)->
    todo.trigger "visible"

  filterAll: ->
    Todos.each this.filterOne, this

  # general attributes for a new Todo item
  newAttributes: ->
    title: this.$input.val().trim()
    order: Todos.nextOrder()
    completed: false

  # when you hit return in the todo input field, create a new Todo model
  # persisting it to localStorage
  createOnEnter: (event)->
    if event.which != ENTER_KEY || !this.$input.val().trim()
      return
    console.log "creating a new Todo" 
    Todos.create this.newAttributes()
    this.$input.val ""

  clearCompleted: ->
    _.invoke Todos.completed(), "destroy"
    return false

  toggleAllComplete: ->
    completed = this.allCheckbox.checked

    Todos.each (todo)->
      todo.save {"completed": completed}

In the above code, we are listening for events like keypress inside the input for a new Todo. Whenever there is a keypress, we delegate to the createOnEnter function. This is where we check to see if the key the user has pressed was the Enter key. If it is, we then take all the text the user has entered in the input, and create a new instance of the Todo model and save it. That causes a change to the TodoList collection, and in the newly defined view, we are also listening for changes to the collection, among other things. When a new Todo is added to the collection, we then create a new instance of the Todo View for individual Todo items, and append it to the todo list in our index.html identified by the code this.$(“#todo-list”) .

All that remains now is to instantiate our AppView defined above, which will set up the rest for us. So now create a new file at BackboneTodos/js/app.coffee, which will simply create a new AppView and allow us to fully interact with the app:

`
var app = app || {};
ENTER_KEY = 13`
jQuery(document).ready ->
  ENTER_KEY = 13
  new AppView()

Now you should be able to enter a new Todo, as well as double click to edit an existing Todo. You can also delete a Todo by clicking on the button to the right of each Todo. But what about if we want to see only Completed or only Active Todos?

We already defined filter functions for this in our router file and our main view and the collection itself. So now, all that remains is to add a template at the bottom of the page with links pointing to routes that trigger the filters for completed or active Todos.

Back in index.html, below the template with an id of “item-template”, create a new template for the footer, with the following code

<script type="text/template" id="stats-template">
      <span id="todo-count"><strong><%= remaining %></strong> <%= remaining === 1 ? 'item' : 'items' %> left</span>
      <ul id="filters">
        <li>
          <a class="selected" href="#/">All</a>
        </li>
        <li>
          <a href="#/active">Active</a>
        </li>
        <li>
          <a href="#/completed">Completed</a>
        </li>
      </ul>
      <% if (completed) { %>
      <button id="clear-completed">Clear completed (<%= completed %>)</button>
      <% } %>
    </script>

Now trying clicking on the links for Active or Completed or All todos. You should be able to see the route change, and to see only the todos that match the filter of “active” or “completed”. Here is the code repository on Github again if you need it.

That’s how we create a Todo app with Backbone.js and CoffeeScript! Backbone is an exquisite library, useful for many different types of apps, and scales insanely well since it’s such a lightweight lib. CoffeeScript will add elegance and maintainability to your JavaScript code so the whole set up is a great leap ahead for making JavaScript apps. Have fun!

 

Leave a Reply

Your email address will not be published. Required fields are marked *

Recommended
Recommended
For web developers and designers out there, in an effort...