24 April 2008

Cross-browser data binding with jQuery

From version 4.0 Internet Explorer has had the ability to bind certain HTML elements to a client-side data source. Pretty much any data you can access with ADO you can use as a data source but the one we've used most commonly at work is XML. By adding some attributes to your HTML elements e.g.

<input type="text" datasrc="#dsoComposers" datafld="compsr_last"/>

you can get them to render out the data from the data source and, in the case of inputs, persist any changes back to the source. Additionally, if you have a multi-row data source and you bind it to a table the contents of the tbody tag are duplicated for each row.

This is very useful for making web applications because:

  • you only send the raw data along with a small amount of rendering HTML to the client
  • the client edits the data, adding/deleting rows without postbacks
  • the client posts back structured data rather than a ton of awkwardly named querystring variables
  • you need no presentation code for translating your data to or from HTML

All this considered I thought I'd have a go at creating a simple cross-browser version of this databinding functionality in jQuery with a limited feature set.

Features to implement

For this proof of concept I decided the features I would try and implement were:

  • Support for JSON as a data source
  • Repeat rendering of a table's tbody section for each item in an array of objects
  • CSS class name based way of tying properties of the objects to HTML elements
  • Rendering of property values within HTML elements innerHTML or value
  • Attaching of event handlers to persist data changes on inputs back to the array data source
  • Some ability to reflect programmatic changes to the underlying data source in the bound HTML elements
  • Doing the above without needing to fully refresh the HTML every time

Use

Some "template" HTML...

    <table id="info">
      <thead>
        <tr>
          <th>Common name</th>  
          <th>Origin</th>
          <th>First developed</th>
          <th>Use</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td class="field[name]"></td>
          <td><input type="text" class="field[origin]"/></td>
          <td class="field[developed]"></td>
          <td class="field[use]"></td>
          <td><button class="field[delete]">Delete Row</button></td>
        </tr>
        <tr>
          <td colspan="4" style="border-bottom: 2px solid #888;"><textarea class="field[comments]" style="width: 100%;"></textarea></td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <td colspan="4"><button id="add">Add Item</button></td>
        </tr>
      </tfoot>
    </table>

...some data...

var data = [
   { name: 'Braeburn', origin: 'New Zealand', developed: '1950s, United States', comments: '', use: 'Eating' },
   { name: 'Bramley', origin: 'Southwell, Nottinghamshire, England', developed: 'about 1809', comments: '', use: 'Cooking' },
   { name: 'Cox\'s Orange Pippin', origin: 'Great Britain, New Zealand', developed: 'c. 1829', comments: '', use: 'Eating' },
   { name: 'Empire', origin: 'New York', developed: '1966', comments: 'Lovely white subacid flesh. Tangy taste.', use: 'Eating' },
   { name: 'Granny Smith', origin: 'Australia', developed: '1868, Australia', comments: 'This is the apple once used to represent Apple Records. Also noted as common pie apple.', use: 'Eating or cooking' }
];

...and a small amount of JavaScript...

$(document).ready(function(){
   $('#info').databind(data);
});

The add button handler looks like this...

data.push({ name: '', origin: '', developed: '', comments: '', use: '' });
$('#info').databind(data);

...any code making programmatic changes to the data must recall databind to refresh the bound HTML elements.

Demo

I've set up this quick proof of concept here. I haven't looked at how it performs with large arrays of data but I expect the answer would be badly.

Going forward there may be scope for turning this into a proper jQuery plugin with support for other types of data source and the ability to bind an arbitrary group of elements to some data rather than just a table.

Links

5 comments:

juust said...

You might want to have a look at the ideas of
mark gibson on using the dom mutation event spec to mimick the .dirty form property (I dont know if ms still use it).

Nice post by the way.

Dave said...

Great criteria for data binding! I thought everyone had gone insane by creating granular templates or over-complicated grids. Just one request. I am a bit of a fanatic when it comes to optimal performance, or not doing work that is not necessary. Would it be possible or even wise to allow for incremental updates to the table. It seems a bit of an overkill to remove and re-add all the DOM elements for the table if only 1 has been added. Give me your thoughts.

Derek Fowler said...

It would be possible, yes, and it's one of the "Features to implement" items I mentioned in the post. This was just a quick proof of concept to see how it would work which I haven't taken any further.

Obviously now there are libraries like Knockout about and you can use the "data-" attributes in HTML5 rather than hacking with class in order to keep your HTML valid.

If there is a need for and folks would be interested in a lighter-weight simple binding library I'd be happy to kick it off or get involved with someone else's project.

Dave said...

Had a look at Knockout, looks interesting. One question however. If you want to create a web application that does CRUD work, uses pagination, uses JSON and AJAX what would you use. We have looked at just using JQuery with Plugins, using GWT, using Dojo or using ZK (last one sounds good, but is stateful, don't like that).

Derek Fowler said...

That's a pretty wide variety of technologies you list and a difficult question to answer not knowing who your end users are, what platform you use and what languages and frameworks your developers are familiar with.

If what you're looking for is a rich but performant client experience then you should also consider Silverlight or Flex (Flash) talking directly to a web service.

If HTML/JavaScript is what you need and you'd like to just slap a pre-built datagrid on a page pointing at some endpoints for read and update operations then, from a server-side technology agnostic point of view, I'd look at dojo or maybe YUI.