Backbone Routed Tabs Example

Using JQuery and Backbone as a simple application framework

This project is maintained by georgefrick

Bringing Order To Chaos.

By extending Backbone.js Router to add, remove and manage an instance of JQuery UI Tabs; you can build a single page web application without the mess. Organize your code into "modules", and launch the modules within tabs.

Each module can be developed independently; giving the application a real 'windowed' feel on a single page. The application is focusing on kick starting applications and helping users learn backbone. From there you can use it as an application skeleton; or just a fun example to refer back to.

But I Don't Know Backbone?

  1. Nick Gauthier's Introduction To Backbone
  2. If you only have 30 minutes.
  3. Tuts Plus on Backbone.js
  4. Learning the RESTful side.

See It Live

Backbone Routed Application Live Demo

Features:

  1. Support for singleton tabs, reopening selects the previously opened tab.
  2. Support for multiple tabs; uniquely identified.
  3. You can easily add tab history, re-open, etc. (Not included)
  4. Scope is managed; parts of the application can be built individually and scope to their portion of the page.

How It Works

Create a special Backbone.Router, that has the two templates needed for adding a tab to Jquery UI Tabs; the list element and the content.

Application.Router = Backbone.Router.extend({
  initialize:function (options) {
    this.el = options.el;
    this.tabTemplate = Handlebars.loadTemplate("tab");
    this.tabContentTemplate = Handlebars.loadTemplate("tabContent");            
  },

Create the routes; similar to struts, this will be "what urls go where"; then add a blank function so that the url never looks like my.com/my.html#thelastcommand

  routes:{
    "":"showHome",
    "newBookList":"newBookList",
    "newTodoList":"newTodoList",
    "newCalculator":"newCalculator",
    "closeAll":"closeAllTabs"
  },
  showHome:function () {
    // do nothing
  },

Create the addTab function. This function adds a new tab to the user interface, and returns a unique id identifying the div where the content goes.

The user can provide just a name, or everything (name, content, id)

    addTab : function(tabInfo) {
      var current = {
        // Defaults
        'name' : "New Tab",
        'content' : "",
        'show' : true,
        'id' : Math.guid()
      };
      var attr, val;
      var attrs = {};

      // Just name passed in? or an object full of properties.
      if (typeof tabInfo === 'object') {
        attrs = tabInfo;
      } else {
        attrs.name = tabInfo;
      }

      // Look for passed in options that override defaults and set them.
      for (attr in attrs) {
        val = attrs[attr];
        if (current[attr] !== undefined && !_.isEqual(current[attr], val)) {
          current[attr] = val;
        }
      }

      // Construct the new tab, consisting of the tab and the tab content.
      this.el.find(".ui-tabs-nav:first").append(this.tabTemplate(current));
      this.el.append(this.tabContentTemplate(current));
      this.el.tabs("refresh");

      // If the user opted not to show the tab, open in the background.
      if (current.show) {
        this.selectTabById(current.id);
      }
      return current.id;
    },

Time to add that ability to select by id. Just look for the tab with that URL and get the parent index. This is the newer JQuery UI 1.9 way of doing it. There is also a check to make sure the tab is open.

    selectTabById : function(tabId) {
      var index = $('#tabs a[href="#' + tabId + '"]').parent().index();
      return $("#" + tabId).length 
               && $("#tabs").tabs( "option", "active",index);
    },

Another example of tab manipulation; this will close everything but the 'home' tab. You could have it close that too of course.

    closeAllTabs:function () {
      var tabCount = $('#tabs >ul >li').size();
      while (tabCount > 1) {
        var tab = $( "#tabs" ).find( ".ui-tabs-nav li:eq(1)" ).remove();
        var panelId = tab.attr( "aria-controls" );
        $( "#" + panelId ).remove();
        $( "tabs" ).tabs( "refresh" );
        tabCount--;
      }
      Backbone.history.navigate(""); // for now.
    },

Now, lets open a simple module. This calculator module can be opened as many times as the user wishes. They'll get a new calculator and can operate separately on all of them. Shown here, is the routing function that creates an instance and places it in a tab.

  newCalculator:function () {
    // For the calculator, we allow multiple tabs...
    var tabId = "calculator-" + Math.guid();
    var tabName = "Calculator";
    var calc = new Calculator.CalculatorView();
    var uniqueId = this.addTab({
      'id' : tabId,
      'name' : tabName
    });
    var tabContent = $("#" + uniqueId);
      tabContent.empty();
      tabContent.append(calc.render().el);                       
      Backbone.history.navigate("#");
    },

What about something more important; where the user can only have a single instance open? We can track the unique ID and check if it is still on the page. Other than that, creation of the tab is similar to the calculator example.

    newBookList:function () {
      if (!this.openBookShelf 
           || ($("#" + this.openBookShelf).length == 0)) {
        var tabId = "book-shelf" + Math.guid();
        var tabName = "Book Shelf";
        var shelf = new BookDatabase.Shelf();
        this.openBookShelf = this.addTab({
          'id' : tabId,
          'name' : tabName
        });
        var tabContent = $("#" + this.openBookShelf);
        tabContent.empty();
        tabContent.append(shelf.render().el);
      } else if (this.openBookShelf) {
        this.selectTabById(this.openBookShelf);
      }
      Backbone.history.navigate(""); // for now.
    },
  });

You could very easily create a Backbone model/collection pair to track open tabs and even remember them; since the tab was opened by a URL. Also, because of this URL setup, a the user can be sent to the application with a specific tab open.

I encourage you to review the rest of the source code and even run the application. I would love any feedback.

Running The Application

Simply build the application with Maven and deploy to Tomcat 7.

Support or Contact

You can contact the author (@georgefrick) through GitHub or via email at george.frick@gmail.com.