In-Depth

Better Enterprise Data Grids with Backgrid

Use Backbone & Backgrid to rejuvenate (or rescue) a data grid that's been pushed well beyond its original design.

The two potential problems we're going to set out to solve with Backbone today are pretty common ones – either getting an older grid view to start performing better, or bringing something new to your client's newfound Web 2.0 expectations. Let's say there's a client you've been working with for years that originally had some of their data set up in a grid view. This is a common pattern for many organizations that need their application to present a collection of data in a readable table-like view. Over time, the business requirements either change or expand, which requires expansions of this grid view.

As data from different sources are stitched together, the cost of fetching a single item grows and grows. It's definitely not unheard of for these once slick and fast grid views to become a sore thumb for the entire application, with horrifyingly long load times and borderline unusable behavior. Often, there's just too much data coming down and further query optimization will not yield the results your client is looking for. Luckily, client-side frameworks such as Backgrid (which depends on Backbone) can solve this problem.

When you're working on the client, you have the freedom to request data either piece-wise or on-demand, which gives you much more flexibility in how you can manage the data requests necessary to render out the grid view. With server-side-only technologies, you're forced to send down everything the user will need until they change the page, which eventually piles up and creates the non-responsive experience we'll be solving.

In order to get to this place, we'll be demonstrating a few of the powerhouses today in client-side, enterprise-level Web development, including the Backgrid grid view library and the Backbone MVC Framework on which it relies.

Let's say you have a list of customer orders that looks something like this (in JSON, of course):

{
  "Phone": "030-0074321",
  "PostalCode": 12209,
  "ContactName": "Maria Anders",
  "Fax": "030-0076545",
  "Address": "Obere Str. 57",
  "CustomerID": "ALFKI",
  "CompanyName": "Alfreds Futterkiste",
  "Country": "Germany",
  "City": "Berlin",
  "ContactTitle": "Sales Representative"
}

This will be the conceptual starting point. However you deliver this serialized data to the page is up to you (it will have to be AJAX-based, or else you'll come across the same problems you started with). This article assumes that this list of orders is available at the generic endpoint of "/api/order".

If you're following along with your own code, you can start by writing all the HTML that will be needed for this example. It's not much, as you can see:

<div id="root" class="container">
  <h3>Northwind Co. Order View</h3>
  <div class="clearfix">
    <span id="addOrderButton" class="btn pull-right">Add Order</span>
  </div>
  <br />
  <div id="tableWrap"></div>
</div>
<script src="js/app/run.js"></script>

Before setting up the grid, there's a few Backbone objects that need to be created that mirror the server-side entities and structures. A working knowledge of Backbone is assumed for this article, so these will be presented fairly quickly.

The grid view that Backgrid creates is based on a Backbone Collection: In this case, a collection of Order models. A good deal of the action in Backbone takes place on the Collection object, and the objects that are extended from it. Backbone Collections usually handle communication with the server, as well as most of the filtering logic in an application. Because you're essentially building a "smart" list of data, a good deal of the focus for this specific application will be on setting up your Backbone Collection objects.

In addition to Collections, Backbone also provides base "classes" (careful; if you're not used to JavaScript ideas, it's not the classical Java/C# idea of classes) for the idea of Models, which make up collections and views. Views are the most dynamic of the three, and often can be seen playing the role of the View and that of the Controller in the model-view-controller (MVC) concept.

In order to keep applications organized, a common practice is to set up a root Backbone object extended off of Backbone View, where the higher-level routines of your application are kept. In addition, because JavaScript code all share the same operating environment, it's good practice to expose as few global variables as possible. Exposing just one is almost always possible. In code, this app's adherence to these principles looks like the following:

/* Smartgrid.App definition */

// Namespace for our app
var Smartgrid = Smartgrid || {};

_(Smartgrid).extend(function () {

  // A root Backbone.View to hold all the App's functionality
  var App = Backbone.View.extend({

  });

  // Return the value of App, so it may be added to the Smartgrid namespace
  return {
    App: App
  };

}());

Now that the namespace and root app object are set up, the two objects needed for Backgrid to understand the data can be set up. These are the Order Model and the corresponding Order Collection.

/* Smartgrid.Order definition */

// Namespace
var Smartgrid = Smartgrid || {};

_(Smartgrid).extend(function () {

  // Model Definition
  var Order = Backbone.Model.extend({
    defaults: {
      Phone: "",
      PostalCode: "",
      ContactName: ""
    }
  });

  return {
    Order: Order
  };

}());

There's nothing too extraordinary going on here; all the frills can be added on a per-implementation basis. The second item that needs to be created is the Backbone Collection, which is mostly defined using the model that's already been created:

/* Smartgrid.OrderCollection definition */

// Namespace
var Smartgrid = Smartgrid || {};

_(Smartgrid).extend(function () {

  // Collection Definition
  var OrderCollection = Backbone.Collection.extend({
    url: '/api/order',
    
 model: Smartgrid.Order,
    
  });

  return {
    OrderCollection: OrderCollection
  };

}());

There's a few things to point out for those unfamiliar with Backbone syntax. The URL field has been set to /api/order, the generic endpoint mentioned earlier. This will likely change from project to project, so be sure to change this out for the endpoint that makes sense for your context.

The last step before getting into the Backgrid implementation and getting to a point where the data is visible is to actually load these files. Then, create a new instance of the Smartgrid.App you defined. One pattern to follow here is to use a "run.js" file that loads in the necessary files asynchronously. In this example, a tiny library called head.js is used:

var app;

// The function to run after all the necessary files are loaded
function init() {
  app = new Smartgrid.App({ el: $('#root') });
}

head.js(
  // Libraries
  "/js/lib/jquery.js",
  "/js/lib/underscore.js",
  "/js/lib/backbone.js",
  "/js/lib/backgrid.js",
  // App
  '/js/app/app.js',
  '/js/app/order.js',
  '/js/app/orderCollection.js',
  // Callback
  init
);

If you now open up a browser such as Chrome to check out what's available in the console, you'll be able to type in "app" and find the instance of the Smartgrid.App running on your page. Now let's make the app do something more interesting.

A logical starting point would be to give the root App view an initialize function. For those familiar with Backbone, the initialize function is something close to a constructor; it's the first thing done when creating a new view, model or collection. To solve the problem at hand, the initialize function for this root view should look something like this:

var App = Backbone.View.extend({

    initialize: function () {
      this.orders = new Smartgrid.OrderCollection();
      this.orders.fetch();
    }

});

In this sample, a new OrderCollection is created and then assigned to the field "orders" on this specific instance of Smartgrid.App (in this example, there's only one Smartgrid.App, but conceptually there could be many). After this collection has been attached, the fetch method is invoked, which instructs the collection to perform an AJAX call to the appropriate URL to populate itself. This is why the URL field's string was supplied on the OrderCollection definition.

Models are now being updated client-side in their Order instances, but you'll also want to start persisting changes made using the grid to the server. Luckily, Backgrid's internal workings trigger an event on the model to which you can subscribe. Saving after an edit is as simple as hooking into this event:

var Order = Backbone.Model.extend({
    
    idAttribute: "OrderId",
    
    initialize: function() {
      this.on('backgrid:edited', this.didEdit, this);
    },
    
    didEdit: function (model, cell, command) {
      model.save();
    }
    
  });

Now that the Order and OrderCollection are set up, the code's ready to get the actual grid initialized. To do this, add an initGrid method to the root app object, which will be called in the existing initialize method:

var App = Backbone.View.extend({

    initialize: function () {
      this.orders = new Smartgrid.OrderCollection();
      this.orders.fetch();
      this.initGrid();
    },
        
    initGrid: function () {
      this.grid = new Backgrid.Grid({
        collection: this.orders,
        columns: columnSettings
      });
      this.grid.render();
      this.$el.find('#tableWrap').append(this.grid.el);
    }

  });

As you can see, the initGrid method is fairly straightforward: attach a new instance of Backgrid.Grid to the object of interest (in this case the root app object), then tell the grid to render and subsequently append this new view's element to the appropriate place in the DOM.

One part that should catch your attention is the mysterious column Settings variable. This is where you define the basic properties of each column:

var columnSettings = [
    {
      name: "OrderId",
      label: "Order ID",
      cell: "integer"
    },
    {
      name: "CustomerName",
      label: "Customer Name",
      cell: "string"
    },
    {
      name: "Freight",
      label: "Freight",
      cell: "number"
    },
    {
      name: "ShipAddress",
      label: "Address",
      cell: "string"
    },
    {
      name: "ShipCountry",
      label: "Country",
      cell: "string"
    },
    {
      name: "ShipPostalCode",
      label: "Postal Code",
      cell: "string"
    }
  ];

This array literally tells Backgrid how to set up the columns. The name value should map to the corresponding attribute on the order model, the label value will serve as the column's "human" title, and the last value, cell, states the type of cell into which you would like that data rendered.

Backgrid comes out of the box with many of the common data types you might need: String, Datetime, Time, Number, Integer, Uri, Email, Boolean and so on. More specialized cells are available through plugins built for Backgrid; you can also subclass Backgrid.Cell yourself to suit the specific needs of your grid.

At this point, assuming the server-side REST API has been set up correctly, there's a working Grid View synchronizing edits back to the server in real time. There's one problem, however: there's no way to add a new entry. This can be solved with a quick addition to the page's markup, and a corresponding event on the root app object:

events: {
      "click #addOrderButton": "addOrder"  
    },
    
    addOrder: function() {
      this.orders.create({});
    },
}

While a huge hurdle has been overcome with this relatively simple application, there's still a good deal of room for progress. The most obvious improvement would be to make this application work in real time in the other direction, perhaps by utilizing WebSockets and a NodeJS backend.

The client-side world has come of age and is ready to start finding itself in the rigors of enterprise-level Web solutions. The idea of taking browser-based technologies seriously has been bubbling up through the world of development for a while, championed by some of the biggest players on the Web. It's getting as far from the JavaScript you loved to hate in the 1990s as C# is from C++. If you've been looking at client-side development but haven't figured out a good approach, we'd encourage you to consider looking into a Backgrid/Backbone-based data grid the next time this specific problem lands on your desk.

About the Authors

Nick Martin is Co-Founder of the Chicago Backbone Group, as well as a Front End Web Developer specializing in JavaScript development at Adage Technologies. His client list includes the Lyric Opera of Chicago, Boston Symphony Orchestra, LA Opera, the Adrienne Arsht Center for the Performing Arts, and more.

Jeff Meyers is Co-Founder of the Chicago Backbone Group as well as a Developer specializing in JavaScript development at Adage Technologies. Jeff has used the Backbone framework in several client projects to deliver the performance and engaging interfaces that many have come to expect of the modern Web application.

comments powered by Disqus

Featured

Subscribe on YouTube