SEO And Accessibility With HTML5 PushState, Part 2: Progressive Enhancement With Backbone.js

In my previous post, I introduced the idea of HTML5′s PushState – a way to manage a browser’s URL without making a round trip to the server to retrieve the information at that URL. In this post, I’ll be taking this information and showing a few example on how we can implement PushState with our Backbone.js applications, providing support for both search engine optimization (SEO) and with only a little more work, accessibility for access via screen-readers and other assistive technologies.

Progressive Enhancement And PushState

PushState allows us to better implement search engine optimization (SEO) and accessibility in our sites by giving us an entry point into progressive enhancement for our pages. We can have our servers render HTML as if we were working with a site that has no enhanced JavaScript functionality. Then, when the browser receives the markup, the css and the JavaScript, we can enhance the user’s experience by providing more functionality within the page than HTML alone would allow.

A Trivial Example: Say My Name!

Before we get into enabling PushState with our Backbone.js code, let’s take a look at a trivial example of progressive enhancement.

Start by having a web server produce a very simple HTML form with a single textbox and button:

<form id="foo">
  Name: <input id="name"><button id="say">Say My Name!</button>
</form>

After the server renders this and delivers it down to the browser, the JavaScript for the page (a Backbone.js view in this case) would pick it up and run with it:

FooView = Backbone.View.extend({
  events: {
    "change #name": "setName",
    "click #say": "sayName"
  },

  setName: function(e){
    var name = $(e.currentTarget).val();
    this.model.set({name: name});
  },

  sayName: function(e){
    e.preventDefault();
    var name = this.model.get("name");
    alert("Hello " + name);
  },

  render: function(){
    // do some rendering here, for when this is just running javascript
  }
});

$(function(){
  var model = new MyModel();
  var view = new FooView({
    model: model,
    el: $("#foo")
  });
});

When i instantiate the view after the page loads, I’m providing the existing content of the form that was rendered by the server to the view instance as the ‘el’ of the view. I am not calling render or having the view generate an for me when the first view is loaded. I have a render method available for after the view is up and running and the page is all javascript, though. This lets me re-render the view later if i need to. (In all honestly, though, this example doesn’t need a render method and it could easily be left out).

Clicking the “Say My Name” button with JavaScript enabled will cause an alert box.

Screen Shot 2011 09 26 at 3 56 12 PM

Without javascript, it would post back to the server and the server could render the name to an html element somewhere.

A Less Trivial Example: A List Of Users

Now say we have a list of users in a `ul` tag. This list is rendered by the server when the browser makes a request and the result looks something like this:

<ul id="user-list">
  <li data-id="1">Bob
  <li data-id="2">Mary
  <li data-id="3">Frank
  <li data-id="4">Jane
</ul>

Now we need to loop through this list and attach a backbone view and model to each of the `<li>` items. With the use of the `data-id` attribute, we can find the model that each tag comes from easily. We’ll then need a collection view and item view that is smart enough to attach to this html.

User = Backbone.Model.extend({});

UserCollection = Backbone.Collection.extend({
  model: User
});

UserListView = Backbone.View.extend({
  attachToView: function(){
    this.el = $("#user-list");
    self = this;
    this.$("li").each(function(index){
      var userEl = $(this);
      var id = userEl.attr("data-id");
      var user = self.collection.get(id);
      new UserView({
        model: user,
        el: userEl
      });
    });
  }
});

UserView = Backbone.View.extend({
  initialize: function(){
    this.model.bind("change:name", this.updateName, this);
  },

  updateName: function(model, val){
    this.el.text(val);
  }
});

$(function(){
  var userData = [
    {id: 1, name: "Bob"},
    {id: 2, name: "Mary"},
    {id: 3, name: "Frank"},
    {id: 4, name: "Jane"},
  ];
  var userList = new UserCollection(userData);
  var userListView = new UserListView({collection: userList});
  userListView.attachToView();

  userList.get(1).set({name: "No Longer Bob"});

});

In this example, the `UserListView` will loop through all of the `<li>` tags and attach a view object with the correct model for each one. It also sets up an event handler for the model’s `change:name` event and updates the displayed text of the element when a change occurs.

Then after we initialize everything and attach it to the HTML, we grab the first item in the collection and change the name. The list is then updated through the use of the event binding:

Screen Shot 2011 09 26 at 4 19 00 PM

With this pattern of implementation in place, it should be fairly easy to see how we can progressively enhance our server-generated HTML with JavaScript frameworks like Backbone.js. However, there’s one more issue to cover still – what happens when we need to render the same HTML from the server side as well as from the client side?

We don’t want to duplicate the HTML templates that our server uses, in our front-end HTML and JavaScript code. This would become a nightmare to maintain. Fortunately, we have some very sophisticated and malleable templating systems in both our server side frameworks and our client side frameworks, these days.

Server Side And Client Side HTML Templates

Looking back at a few of my previous posts, we can see the beginnings of what we need to do, to handle this situation. In my post on Rendering A Rails Partial As A jQuery Template, I covered the subject of re-using an ERB template from a Rails application in the front-end JavaScript with jQuery Templates. In this post, I show how to use a Rails model and populate it with data that looks like the jQuery Template markers:

class SomeController < ApplicationController
  def show
    @template_model = SomeModel.new(:value => "${value}")
  end
end

You can see in this example that I’m populating a model’s `value` attribute with the data “${value}”. When this gets rendered into HTML via an ERB template, it will produce HTML that can be used with jQuery Templates.

In addition to this double-rendering technique, you may want to provide some functional or unit testing around your Backbone.js application code. To handle this with Jasmine-BDD and again re-use the existing templates in the Jasmine fixtures, you can combine the double-rendering of templates with my post on test-driving Backbone views with jQuery Templates.

Backbone.js And HTML5 PushState

With all of this in place – server rendered HTML and progressive enhancement to enable client side JavaScript – we can set our Backbone application up to use PushState. To do this, we only need to enable PushState via the `Backbone.history.start` function:

Backbone.history.start({pushState: true});

Using Backbone’s `history` requires a Router with at least one route, of course. While I haven’t shown how to use a router in this post, there are plenty of example of using routers out there on the web, including my own blog posts on backbone.js and my training course. Once a router is in place, though, and Backbone’s history is started, the browser will have it’s URL updated with full URLs instead of URL hash fragments.

If we have set everything up correctly, a user will be able to hit the root of our application and Backbone will pick up and start the application from there. Then, once the user has progressed through the application and the browser’s URL has been updated appropriately, they can either bookmark the URL, copy & paste it or simply hit the refresh button in their browser. Doing this will cause the browser to make a request back to the server for the page that the browser was previously looking at. Once the page is rendered by the server and sent down to the browser, our JavaScript will kick in and enhance the functionality of the page, allowing the full experience of the page for those browsers that can support it.

The Result: SEO And Accessibility Friendly

By having the server side HTML rendered for us and delivered through standard URLs, our application is much friendlier when it comes to search engine optimizations. We’re most of the way down the path of creating accessibility within our application. The hard part of this is getting the server to render up the HTML that we want while still having the JavaScript enabled browsers make it work nicely. We know we’ve solved this problem simply by support PushState. Now we can head down the rest of the accessibility path by including the necessary html tags and attributes, such as the `alt` attribute for images, HTML5′s `aria-live` attribute, and other bits of information that assist the assistive technologies and other accessibility tools.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Javascript, User Experience. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Ptran

    I was faced with this same predicament on how to handle bookmarks.  This article has guided me to the right direction exploring more on PushState.  Thanks for the great article.

  • olivernn

    I wrote about this earlier in the year - http://blog.new-bamboo.co.uk/2011/2/2/degradable-javascript-applications-using-html5-pushstate

    Whether you use Backbone or any other library, pushState certainly makes it much easier to produce easily degradable javascript applications

    • http://mutedsolutions.com Derick Bailey

      nice article! i especially like the discussion of reusable templates. that’s something i’ve been looking at and have seen mustache mentioned numerous times, but haven’t had a chance to try it out yet.

      (btw: i love your pusherapp product! i’m using it in my http://bluefinpad.net project. it’s great!)

  • http://nieve.heroku.com Nieve

    I came up with a different way to handle server side templates rendering since I am working with MSMVC, the idea in short is to render the template html, replacing keys with the values. If anyone wishes to have a look, I posted about it here: http://nieve.heroku.com/post/progressive_enhancement_for_jquery_templates_with_mvc

    Would really appreciate some feedback, whether it makes sense or I missed something.

  • Ad

    Awesome post, Derick! However, I still don’t think libraries like backbone play nicely with prog. enhancement. I think you could get away with an “html message pattern”, relying on the server and controller to return rendered html and use less code. While your javascript code may not be as clean this way, your overall solution will be easier to maintain in the long run IMHO. PushState is awesome, btw –thanks for sharing it!

  • Mark

    You are filling your collection with userData. Normally this data is available on the server. Do you suggest to load this data from the server again(so its called twice, 1. for rendering 2. for loading the collection or do you suggest to generate the model from the view(li) itself?

    • http://mutedsolutions.com Derick Bailey

      I would not recommend building the model from the data in the html elements. You would end up storing a lot of data that is not needed in the DOM in order to make this work. It would also become very slow and cumbersome for the end user, as DOM queries are notoriously slow in comparison to loading the data as JSON.

      So, yes, you’ll end up handling the data twice: once on the server to render the initial HTML, and then again on the client to populate the backbone models / collection. 

      SEO, accessibility and pushState aren’t “free” – there are tradeoffs involved, and this is one of them. If you need pushState, though, then the double processing of the data is a fairly small price to pay, in my experience.

    • http://mutedsolutions.com Derick Bailey

      hi again,

      i ran across this via twitter and thought you might get some value out of it: http://42floors.com/blog/posts/user-authentication-with-rails-and-backbone-js

      • Mark

         Great this is helping me a lot, even though I am not using rails(java backend, I plan to use mustache for templates as I can use them in java as well) but I think I can take over some stuff!

  • Fsdafdasdfa

    fsadfaf

  • Michael Wu

    Thanks Derick, for this informative post. Following on Mark’s question, first of all, would you say there is a way for the server to send back the rendered HTML along with the data that will be used to populate the backbone models? Second, this is more of a clarification of backbone, using your method of server side rendering of the original DOM, it looks like the routing will be done through the server instead of the backbone router? Is that a good practice?

    • http://mutedsolutions.com Derick Bailey

      Hi Michael,

      #1) standard data boot-strapping can be done here, where you render server side data directly in to the HTML as a javascript variable and then load it in to your models and collections.

      #2) Both – whenever you are using pushState, your server and your backbone router have to both be able to handle the request appropriately.

      hope that helps.

    • Jon

      Michael, some info on bootstrapping your Backbone app with JSON data embedded in the page – http://backbonejs.org/#FAQ-bootstrap

  • mattcrider

    Are there any frameworks or projects (PHP preferably) where this consideration is built in? E.g. a framework where controllers send back a fully function static html page by default but JSON data if requested by Backbone — and shares the same templates between the front end and back-end?

  • http://www.habdas.org/ Josh Habdas

    Derick, I’ve been following your work for a few months and am a zealot for web standards. While I understand PE is achievable in a RIA using the “Holy Grail,” as Airbnb refers to it in their article “Our First Node.js App: Backbone on the Client and Server” (http://nerds.airbnb.com/weve-launched-our-first-nodejs-app-to-product), the a practical approach seems seems elusive and I continue to see many uninformed individuals pointing others towards the hashbang approach simply because Google is still hosting the documents.

    Would you be able to list a set of related reading that might help those of us trying to improve “time to content” in a way congruent with usability and this here fine article? It seems to me that, because Google is considering page speed as a metric in the overall PR algo, and as a result of the concept of cloaking, there might be some disadvantages to sniffing crawlers and delivering a modified experience beyond what meets the eye for those looking for the quick fix.

    Thanks for all you thought work and for writing in a clear, straightforward manner. And keep working on those conference talk proposals! ;)

  • Bill

    How would I do this in Marionette.js?

  • Denis

    Does it mean that we have to have rendering on both sides (browser and server)? So the BB router would be similar to say Expressjs router? Wouldn’t that be a nightmare to maintain? I’m developing app where I have to have server side rendering, and it seems that BB doesn’t fit in the picture.