Over a million developers have joined DZone.

Exploring Client-Side MVC with Backbone.js

· Web Dev Zone

Backbone.js continues to gain popularity in the JavaScript MVC community. I decided to give it a try by creating a simple, single-page app to CRUD a single domain model.

While it wasn’t as trivial as a traditional server-side implementation in Rails, it did turn out relatively clean. This is a long post, and if you make it through it, let me know what you think.

Originally Authored by Jared Carroll


Server-side Setup

Our server-side is a stock Rails 3.1 app. We’ll go with a classic blog app consisting of a single Post model to CRUD.

$ gem list rails
*** LOCAL GEMS *** 
rails (3.1.3)
$ rails new blog
$ cd blog
$ rails g scaffold post title:string body:text
$ rake db:migrate
$ : > app/views/posts/index.html.erb  # remove the default markup from posts/index.html.erb (this will be our app's homepage)


We’ll use the backbone-on-rails Ruby gem to add backbone.js and underscore.js (backbone.js’s only dependency) to our Rails app.

$ echo "gem 'backbone-on-rails'" >> Gemfile
$ bundle install
$ rails g backbone:install
      insert  app/assets/javascripts/application.js
      create  app/assets/javascripts/collections
      create  app/assets/javascripts/models
      create  app/assets/javascripts/routers
      create  app/assets/javascripts/views
      create  app/assets/templates
      create  app/assets/javascripts/blog.js.coffee


The backbone.js and underscore.js files are not copied into your Rails app’s directory. They’re both automatically loaded by the backbone-on-rails gem (the jquery-rails Ruby includes jquery.js and jquery_ujs.js the same way).

Let’s use the scaffold generator from backbone-on-rails to generate our client-side Backbone.js objects.

$ rails g backbone:scaffold post
      create  app/assets/javascripts/models/post.js.coffee
      create  app/assets/javascripts/collections/posts.js.coffee
      create  app/assets/javascripts/routers/posts_router.js.coffee
      create  app/assets/javascripts/views/posts
      create  app/assets/javascripts/views/posts/posts_index.js.coffee
      create  app/assets/templates/posts
      create  app/assets/templates/posts/index.jst.eco


We’ll be using CoffeeScript, the Rails default client-side language, throughout our Backbone app. CoffeeScript’s cleaner, Ruby-like syntax will help make the code more readable and understandable.

Listing Posts

We’ll start our app by implementing a “Read” action, the homepage. The homepage will be a list of all our posts. The backbone-on-rails scaffold generator has already created for us a router, an index view and template, a collection, and a model. To wire up the homepage we’ll need to modify our router.

Backbone routers are like controllers in Rails. They map routes to actions. The empty route represents the default action, in our case, the homepage.

app/assets/javascripts/routers/posts_router.js.coffee

class Blog.Routers.Posts extends Backbone.Router
  routes:
    '' : 'index'
 index: ->
    posts = new Blog.Collections.Posts
    new Blog.Views.PostsIndex collection: posts
    posts.fetch()

In our index action, we create a Posts collection, a view for the collection, and then tell the collection to fetch its posts from the server. We don’t tell the view to render itself because the fetch is asynchronous. We’ll see later how the view uses an event handler to render itself after the fetch.

The Posts collection is configured to make server-side requests to our Rails app at “/posts” and to contain Post models.

app/assets/javascripts/collections/posts.js.coffee

class Blog.Collections.Posts extends Backbone.Collection
  url: '/posts'
  model: Blog.Models.Post

Our Post model is as simple as it gets. It’s just a Backbone.Model subclass and contains no custom logic.

app/assets/javascripts/models/post.js.coffee

class Blog.Models.Post extends Backbone.Model

Before we take a look at the PostsIndex view, let’s first quickly go over the Rails layout used on the server-side.

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Blog</title>
    <%= stylesheet_link_tag    "application" %>
    <%= javascript_include_tag "application" %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <div id="app">
      <%= yield %>
    </div>
  </body>
</html>

The only change we’ve made to this default Rails layout is the addition of a single “#app” <div>. This <div> will act as the container for our app. Each Backbone view will replace its contents.

Now we can take a look at the Backbone view.

app/assets/javascripts/views/posts/posts_index.js.coffee

class Blog.Views.PostsIndex extends Backbone.View
  el: '#app'
  template: JST['posts/index']
  initialize: ->
    @collection.bind 'reset', @render, @
  render: ->
    $(@el).html(@template())
    @collection.each (post) =>
      view = new Blog.Views.PostsItem model: post
      @$('#posts').append(view.render().el)
    @

On initialization, the view binds its render method as a handler to its collection’s reset event (this is the Posts collection we created in the Blog.Routers.Posts#index action). The Posts collection will trigger a reset event after successfully fetching its posts from the server. When the event is fired, the view will render its template and create and render another view for each Post in its collection.

Here’s the template used by the PostsIndex view:

app/assets/templates/posts/index.jst.eco


<h1>Posts</h1>

<table id="posts">
  <tr>
    <th>Title</th>
    <th>Body</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>
</table>

<br />

<a href="#posts/new">new post</a>

This table will be populated by the view used for each post in the collection:

app/assets/javascripts/views/posts/posts_item.js.coffee


class Blog.Views.PostsItem extends Backbone.View
  tagName: 'tr'
  template: JST['posts/item']
  render: ->
    $(@el).html(@template(post: @model))
    @

And its template:

app/assets/templates/posts/item.jst.eco


<td><%= @post.get 'title' %></td>
<td><%= @post.get 'body' %></td>
<td><a href="#posts/<%= @post.id %>">show</a></td>

There’s one last thing we need to do before we can view our homepage in a browser: we have to bootstrap our app’s router.

app/assets/javascripts/blog.js.coffee

window.Blog =
  Models: {}
  Collections: {}
  Views: {}
  Routers: {}
  init: ->
    new Blog.Routers.Posts
    Backbone.history.start()

$(document).ready ->
  Blog.init()

An onDOMReady event handler invokes an init function that creates a PostsRouter and tells Backbone to start monitoring all hashchange events. All our client-side pages will use hash fragments as URLs. Backbone will listen for hash fragment changes and route them using the routes specified in our PostsRouter.

Finally we can now start our Rails server and hit our app at http://localhost:3000/posts. Unfortunately, we don’t have any posts yet. So let’s move on to creating a post.

Creating a Post

At the bottom of our existing home template is a link to create a new post:

app/assets/templates/posts/index.jst.eco

<a href="#posts/new">new post</a>

This means we need to add another route and action to our PostsRouter.

app/assets/javascripts/routers/posts_router.js.coffee

class Blog.Routers.Posts extends Backbone.Router
  routes:
    '' : 'index'
    'posts/new' : 'new'
  index: ->
    # same code from above...
  new: ->
    post = new Blog.Models.Post
    posts = new Blog.Collections.Posts
    posts.bind 'add', =>
      triggerRouter = true
      @navigate '', triggerRouter
    view = new Blog.Views.PostsNew
      collection: posts
      model: post
    view.render()

In our new action, we create a Post model, then create a Posts collection and bind an anonymous handler to its add event. A collection will fire an add event whenever a new model is added to it. Our add event handler performs the equivalent of a server-side redirect by navigating the router to the “empty” router (the homepage). Finally we create and render a view to submit a new post. app/assets/javascripts/views/posts/posts_new.js.coffee

class Blog.Views.PostsNew extends Backbone.View
  el: '#app'
  template: JST['posts/new']
  events:
    'submit form' : 'create'
  render: ->
    $(@el).html(@template(post: @model))
    @
  create: (event) ->
    event.preventDefault()
    @collection.create
      title: @$('#title').val()
      body: @$('#body').val()

The element (PostsNew#el) used in our new post view is the global “#app” <div>. The new post view will replace the contents of this <div> with its own template containing a form to submit a new post.

The view also registers a handler to the submit event from the <form> in its template. This handler stops the default form submission behavior and then asks its posts collection to create a new post. This will result in an HTTP POST request to the server and, if successful, the collection will fire an add event. This is the same event our PostsRouter, in its new action, bound an anonymous “redirect” handler to.

And here is the new post view’s template:

app/assets/templates/posts/new.jst.eco

<h1>New post</h1>

<form>
  <div class="field">
    <label for="title">Title</label><br />
    <input type="text" name="title" id="title" />
  </div>
  <div class="field">
    <label for="body">Body</label><br />
    <textarea name="body" id="body"></textarea>
  </div>
  <div class="actions">
    <input type="submit" value="create" />
  </div>
</form>

<a href="#">back</a>

Great, we can now create posts. However, we can’t let people create posts without a title and a body. Let’s see what we can do to validate our new posts.


Validating a Post

Adding some client-side validation will help improve the responsiveness of our UI. We’ll still validate posts on the server-side, but for the user’s sake, I feel this duplication is worth the tradeoff.

Backbone models include a validate method that can be used to perform custom validations. However, as Rails developers we’re used to ActiveRecord‘s high-level, declarative approach to validation. And thanks to the jQuery validation plugin, we can have declarative validation on the client-side too.

First, download the plugin (after downloading and unzipping it, remember to restart your Rails server).

$ wget http://jquery.bassistance.de/validate/jquery-validation-1.9.0.zip --directory-prefix vendor/assets/javascripts
$ unzip vendor/assets/javascripts/jquery-validation-1.9.0.zip -d vendor/assets/javascripts


Next, add the plugin to our application’s JavaScript manifest file.

app/assets/javascripts/application.js

//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require jquery-validation-1.9.0/jquery.validate
// other default initialization

Now we can add some validation to our new posts.

app/assets/javascripts/views/posts/posts_new.js.coffee

class Blog.Views.PostsNew extends Backbone.View
  # same code as above...
  render: ->
    $(@el).html(@template(post: @model))
    @$('form').validate
      rules:
        title: 'required'
        body: 'required'
    @
  create: (event) ->
    # same code as above...

The jQuery validate plugin allows you to specify per-attribute validation rules. Here we’re requiring a title and a body. The validate method will stop the form submission when validation fails. This will also prevent the new post view’s form submission handler, #create, from executing as well.

Go and give it a try in the browser. Submitting a post without a title and body will now fail and display an error message(s).

Viewing a Post

Ok, so far we can display a list of posts, create a post, and validate a post. Next up is: viewing an individual post.

On our home page, each post row included the following link for viewing the post:

app/assets/templates/posts/item.jst.eco

<td><a href="#posts/<%= @post.id %>">show</a></td>

Let’s add a route and action to our router for this post page (i.e., #show).

app/assets/javascripts/routers/posts_router.js.coffee

class Blog.Routers.Posts extends Backbone.Router
  routes:
    '' : 'index'
    'posts/new' : 'new'
    'posts/:id' : 'show'
  index: ->
    # same code from above...
  new: ->
    # same code from above...
  show: (id) ->
    post = new Blog.Models.Post id: id
    view = new Blog.Views.PostsShow model: post
    collection = new Blog.Collections.Posts [post]
    post.fetch()

This newest route includes a named parameter, :id. Backbone will automatically pass the named parameter’s value as an argument to our #show action.

Currently in Backbone, fetching an individual model isn’t very elegant. Above is one of the cleanest ways. You first create a model with the requested id, then add the model to a collection, and finally, tell the model to fetch itself.

Our #show action also creates an instance of the following PostsShow view for the individual post.

app/assets/javascripts/views/posts/posts_show.js.coffee

class Blog.Views.PostsShow extends Backbone.View
  el: '#app'
  template: JST['posts/show']
  initialize: ->
    @model.bind 'change', @render, @
  render: ->
    $(@el).html(@template(post: @model))
    @

During initialization, the view adds a handler to its Post‘s change event. The Post will fire a change event after it has been successfully fetched.

The element (PostsShow#el) for this view is the same global “#app” <div> container that was used by the post list and new post views. And like those two views, an individual post view will also replace the contents of this <div>.

And here’s the posts show view’s template:

app/assets/templates/posts/show.jst.eco

<p>
  <b>Title:</b>
  <%= @post.get 'title' %>
</p>

<p>
  <b>Body:</b>
  <%= @post.get 'body' %>
</p>

<a href="#">back</a>

The “back” link goes back to our homepage.

Client-side MVC

Backbone.js brings the power and maintainability of MVC to the client-side. Routers can be used like Rails server-side controllers to create responsive, single-page apps. Views are also like controllers, but instead of responding to url changes, they respond to DOM level events, such as link clicks and form submissions. Templates contain the app’s markup, they’re what Rails calls views. Models and collections round out Backbone.js, providing traditional class-based, client-side domain modeling.

We’ve only covered the first two letters in CRUD, but it was a lot! I hope this article can give you some direction in implementing the rest of them. The client-side JavaScript MVC space is growing rapidly. Backbone’s quick and easy Rails integration makes it an easy sell but there are other options. Be sure to research other frameworks before evaluating what works best for you.


Source: http://blog.carbonfive.com/2011/12/19/exploring-client-side-mvc-with-backbonejs/

Topics:

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}