Exploring Client-side MVC with Backbone.js

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.

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.

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.

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.

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

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

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

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

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

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

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

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

And its template:

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

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

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

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

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

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

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

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).

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

app/assets/javascripts/application.js

Now we can add some validation to our new posts.

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

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

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

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

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

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.

About Jared Carroll

After a short stint in the fashion industry Jared found his true calling at Carbon Five. Yes... he looks like a serial killer in this photo. But really he is as gentle as a flower.
This entry was posted in Web and tagged , , , . Bookmark the permalink.
  • Michael Martz

    Check out this post on the difference between server-side and client-side mvc architecture. (http://gmoeck.github.com/2011/03/10/sproutcore-mvc-vs-rails-mvc.html) Although it is about the sproutcore, the principle is pretty similar in backbone.

    • Jared Carroll

      Thanks for the article Michael, Gregory always has great posts.

      I definitely took a stateless MVC approach in this article. Although, I might have to re-write this to use a more stateful approach, similar to Sproutcore MVC, just to see how it would turn out.

  • Pingback: StartupDigest | The best information about the tech startup world

  • Joe C

    Thank you so much this tutorial. It was exactly what I looking for: a complete start to finish comprehensive example. Well done!

  • http://www.dzone.com/ Chris Smith

    Jared,

    I really enjoyed reading your blog and was wondering if you would be interested in having it featured on DZone.com. If so, please contact me for more information.

    Thanks and keep up the great work!

  • http://www.facebook.com/people/Marcel-Overdijk/528329177 Marcel Overdijk

    Is the source code available in github?

  • Pingback: Design2U » [Javascript] Backbone.js (一) Hello backbone

  • http://twitter.com/bbnnt ~~

    Cool, simple and clean source. Thx