2-way data binding with Underscore and jQuery

Posted on December 3, 2013

0


2-way data binding

In Underscore, Data binding by default is one way. You take a template (a snippet of HTML code), “inject” a JavaScript object into that template and get your pre-processed HTML as a result.

This is perfect when your site is read-only. The moment you want to start a dialog with the user, however, you will have to find some way to get input from that user back into some JavaScript Object and (if wanted and needed) back to the Server.

Working with Knockout.js, that offers two-way data binding by default, I felt this to be a perfect challenge to pick up: “can I build a very simple and straight forward solution that allows for 2-way data binding without any boilerplate code?”

This post

After some basic introduction I will talk about two ways of data-binding, only showing the first version: using jQuery, the “class” attribute and an Object Store.

You can skip the intro ans scroll to the images immediately if you like.

Demo

A live demo of 2-way binding can be found here.

Some thoughts on Backbone.js

I had in mind to take a deeper look at Backbone at first.

But looking at Backbone.js I noticed the dependency of Backbone on Underscore.js. A library adding extra functionality when you deal with collections and arrays, but also offering checks like isObject, isArray and isDate which surpasses the (more limited) JavaScript typeof check.

“Web2.0” and abstraction of services/stores

Backbone.s itself offers a set of tools that increases the ease of programming when you start building “Web2.0” type of sites, using Web API’s to deliver raw XML or JSON data instead of pre-formatted HTML. It abstracts the “Services” part (see a little bit about the: Model, View, Controller, Service/Store pattern here. It assumes a separation of the Model into a View Model and an Application Model)

This abstraction allows you to switch between different types of “Services” or “Stores” without any consequences for the code that tries to access that Store or Services.

Abstraction of rendering method

The second thing Backbone does is an abstraction of the actual rendering process. And this is a very important part to the design of Backbone itself.

When data gets loaded or updated (by another source), Backbone provides the pattern to activate the rendering process. Up and until that point, Backbone provides you a clear structure and internal infrastructure to fetch the data and assign several event listeners to that data (including “change”).

The moment your code hits the “render” method in the Pattern Backbone uses, it is completely up to you.

  1. You can use the default Backbone / Underscore rendering logic
  2. You can add and use your own rendering-library, including  Moustache, Handlebars, Dust and what have you not.

And that’s it on Backbone for this post

As this post is not about Backbone, but diving deeper into Underscore, I leave it at this.

Templates in Underscore

Underscore.js offers its own Template method (see here): allowing you to mix JavaScript with HTML code and execute that JavaScript when executing or “activating” the Template.

<% some JavaScript %> <%= someVariable %>

When you process a Template in Underscore, Underscore itself will break apart the HTML to see if any JavaScript is included inside the template: to be executed by Underscore.

That JavaScript is enclosed in the ERB-style delimiters that look like this: <% %> and will be executed by Underscore, when the template is processed. This allows (for one thing) to create iterators with which for instance the content of Arrays can be transformed into HTML.

When you inject a JavaScript object into the Template, it will try and resolve any reference to that object when it evaluates and executes the JavaScript embedded between the <% %> tags.

<div>My first name is <%=firstname%></div>
<div>My last name is <%=lastname%></div>

Will result in the browser to

My first name is Peter
My last name is Kaptein

When the object contains the variables “firstname” and “lastname” and the variables contain the string values “Peter” and “Kaptein”.

Converting the Template to an object that Underscore can handle happens as follows:

var myTemplate=_.Template( TemplateHTML )

Where TemplateHTML contains a String with the template “code”.

Within Underscore you can either inject the object immediately, when processing the text-based Template code, or use the processed template to inject the object later.

var resultHTMLtext=myTemplate( PeterKaptein );

The result of this process is a new String value you can inject into your HTML either as a new node added to the DOM or as text you inject into a specific part of the DOM via innerHTML.

One way binding, no specific pattern and no event model

Underscore only provides binding in one direction: injecting your data into your template and delivering the end result as clear text.

There is no specific pattern you are shown to follow and use (as is the case in i.e. Backbone)

There is no event model (to take action when the object or a list receives updates).

Two way binding with jQuery and Underscore

2 way data binding

2 way data binding

2-way data binding allows you to update your object when the user changes one or more values in the representation of that value (a form or collection of input fields).

Updating all representatives

Updating all representatives

By updating the object, our work is not finished yet, as it is possible that same object is represented at least in one other place in your HTML page. For instance: the list you selected it from.

There are two ways to deal with that.

  1. Re-render — the block that contains the representative. This can be extremely expensive. Not only do you need to repacke the existing block in the DOM with a new one, you also need to re-render the HTML with which you will replace the existing content.
  2. Inject — the new values. Now this is extremely more elegant. You change only what needs to be changed. This injection can take place both on HTML elements and input elements like textarea’s, input fields and radio and checkboxes.

In this proof of concept I chose to inject the new values exactly there where needed and represented.

DIY

The fact that Underscore does not have a default approach for 2-way data binding, does not mean we can not make an attempt to build a process of two-way binding ourselves.

For this I chose to use jQuery and Underscore. Additional I use a “Store” or “Dictionary” based on the possibility withing JavaScript to create any field or variable on an object even after it is instantiated.

The process of storing and retrieving works as follows:

myGlobalStore["myIndex"] = myObject // Storing the object in the "Dictionary"
var myObject = myGlobalStore["myIndex"] // Retrieving that object

This is an essential part of the way I choose to do the double-binding in my specific proof of concept. It is not the only way, but for sure the most simple way I know to avoid a lot of other issues.

Events and the trouble of “event binding”

The template-approach of Underscore (and any approach) and the “not really Object Oriented basis” of both HTML and JavaScript (as compared to Adobe Flash and ActionScript or C#)  makes it very elaborate to bind events on your (generated) HTML.

  1. Binding the representative to the object — I will use two methods to bind the object to the representative. One will use a central Event Bus. The other will bind the object directly to the representative.
  2. Dispatching to the representatives — Once the object is updated, we want to dispatch the changes to the representatives. Again I will use two approaches for this.

The approaches

  1. Using a Central Event Bus and an Object Store — In this approach all events are handled in one single place: the central event bus. This Bus assumes you use the very same pattern for data-binding everywhere.
  2. Using object-specific listeners — In this approach we will bind all HTML elements directly to the object that is represented. We bind all objects using a reference to their ID, to prevent (our HTML) objects to be held in memory (due to object references) when they should be destroyed.

Both approaches have disadvantages as you will see.

  1. Abusing the Class tag to create jQuery collections — To find each and every representative of the object, we will inject the object ID into the class-tag. This will create a bit of overhead when rendering the object as the ID represents a non-existing CSS class. This allows us to treat any object with this specific ID as part of a collection we can retrieve via jQuery. HTML-purists will hate and condemn this approach.
  2. Adding several event listeners to each HTML object — While this is much neater on paper, it requires an immense amount of code and is more elaborate, more complex and less generic than the first solution.

Only the first approach covered

In this post I will now only address the first approach.

Abusing the class-tag for 2-way data binding

We use a Central Event Bus here. What the Event Bus does is the following:

  1. Respond to change-events
  2. Resolve the Concrete Object that is affected by changes in its Representative
  3. Find all Representatives
  4. Inject the new Value(s) into the Representatives

Using reflection, the amount of code needed for this approach is close to zero.

Binding objects and input fields

To minimize all coding, we try to cut away as much boilerplate code as possible.

We are faced with several “challenges” of which the most important one is: how do we know which input field belongs to what variable in our JavaScript object?

Another one is: how do we update all elements that represent the object once we updated one?

This will all be addressed below.

The object store

As said before, in this approach we work with an object store. This store contains all objects we want to present on screen / in the HTML.

Dead objects

How we deal with dead objects (ones we do not need anymore) is beyond the scope of this article.

The code

A vasic Object Store is quite simple in JavaScript. Here is an example.

Note that we do not check whether the object already exists.

// The Object Store is used to refer to objects 
ObjectStore.addObject = function (object: Object, id: string) {
    // Do we have a store?
    if (ObjectStore.objectStore == null) {
        ObjectStore.objectStore = {};
    }

    // Add object with ID as key
    ObjectStore.objectStore[id] = object;
}
ObjectStore.getObject = function (id: string) {
    // Get the object associated to the key value (or ID)
    return ObjectStore.objectStore[id];
}

With this in place we can start creating a completely loosely bound environment in which we can update all kinds of objects without maintaining any reference lists of other objects or events apart from the object we want to use to be reflected in the HTML page.

Reading the changed values from the form

Before we go into other elements, let’s take a look at how the values from the fields in the form are read.

We want to keep this simple.

  1. Pairing names — To keep things minimal we pair the name of the HTML object to the name of the variable in the object.
  2. Using reflection — We then use reflection to match one with the other.

In our HTML Template

<input class="<%=objectid%>" type="text" 
    name="firstname" 
    value="<%=firstname%>"/>

As said before: we retrieve and use the name of the HTML object to find the (matching) name of the Represented variable in the JavaScript object. This allows us to skip a lot of boilerplate code we otherwise would need to match one to the other.

In the JavaScript immediately below you see how we solve the next step.

JavaScript

CentralEventBus.updateObject = function (htmlObject, clusterID) {

    // Get ID
    var idPair = clusterID.split("_");
    var objectID = idPair[0];

    var object = ObjectStore.getObject(objectID);

    // This example limits itself to input fields
    // The collection consists of objects within the same cluster
    // or form
    $("#" + clusterID).find("." + objectID).each(function (index, htmlObject) {
        var fieldName = htmlObject.name
        // Update the value
        object[fieldName]=htmlObject.value;

        // What we know here is the name of the resultObject
        // We use that knowledge to "pull" the data into that object.

    });

    // Update all representatives
    this.updateAllRepresentatives(object);

}

And that is all.

How it works:

  1. The form — has the ID of the object and a unique ID linked to the form itself.
  2. The object ID — Is retrieved from the form ID, splitting it on the “_” separator.
  3. The represented object — is retrieved from the Store
  4. The form DOM — is retrieved from the DOM using jQuery. Within that form we filter all fields (using the object ID in the class-tag)
  5. Each field — in the HTML form is passed and the value from that field is placed into the object variable that has the same name as the Form field.
  6. We update — All representatives of that very object, leading to an update of all values within those representatives.

Wit this we have a full round-trip: updating the object we represent in the HTML page, then updating all representatives with the new values in our object.

Updating all representatives

To update all representatives we need to do some extra work.

In this solution I choose (as said before) to abuse the “class” attribute in the HTML element.

What we need is a simple and secure way to find ALL representatives of the object and the most simple way with jQuery is to create a Object-specific collection of HTML elements by using the “class” attribute.

In the HTML template

<input class="<%=objectid%>" type="text" 
    name="name" 
    value="<%=name%>"/>

As said, the class of the object is abused to make it part of a collection (in this case: that of objects Representing a specific object).

The name of the HTML object helps us to resolve which value we will inject int that object from out JavaScript object.

JavaScript code

CentralEventBus.updateAllRepresentatives = function (object) {
    // Update all input related to object

    // Get the collection of HTML elements 
    // representing values in our object

    // This example limits itself to input fields
    $("." + object.id).each(function (index, htmlObject) {

        // Update the value
        var variableName=htmlObject.name
        htmlObject.value = object[variableName];

    });

}

As you can see, the actual code to do all the work is only 3 lines.

  1. Retrieve all HTML elements from the Collection — bound to the JavaScript Object
  2. Resolve the variable name — Using the name we gave to that HTML element
  3. Inject the (new) value — From the JavaScript object we pass to this function.

Events we use in HTML

The events can be placed either on the input fields/elements or on a button of some sorts, depending what your update/synchronization scheme is.

That event and event handler can be implemented in your Underscore Template as follows:

<input type="button" 
onclick="CentralEventBus.updateObject(this,'<%=id%>_<%=clusterID%>');" />
<input type="text" 
onchange="CentralEventBus.updateObject(this,'<%=id%>_<%=clusterID%>');" />

Closing notes

Binding

Binding between the objects in HTML and in JavaScript is all based on ID.  Any issues with HTML elements persisting in memory due to unwanted references after they should have been destroyed are avoided.

Disadvantages

  1. Possible cost — We rely a lot on jQuery to resolve elements from the DOM. Especially when we display a lot of elements this can become quite costly.
  2. Not the purist way — This is absolutely not how a code-purist would solve 2-way data binding. The fact that we kind of “hack” the resolve of all related elements bound to- and representing our JavaScript object is “no way to go”.

Advantages

  1. Lightweight code — With only a minimum of code we created a full 2-way solution for data-binding. All our HTML elements will update our JavaScript objects when bound properly. All our JavaScript objects will update their HTML representatives (when bound properly).
  2. Scalable — You can create any application of any size using these very lines of code. If and when extra handlers need to be attached you can choose to extend either the objects or the Update process (when the data is injected from HTML elements to JavaScript objects) by registering specific handlers in a separate ObjectStore and applying the principle of Inversion of Control in that approach.
  3. Simple and minimalistic — Regardless the size of your application and the amount of lines you need to build the rest of your application, the binding process remains simple and minimalistic.

A word to the purist

JavaScript is one of the dirtiest programming languages I know. No strong typing. No explicit way to create classes and define the scope of your elements. Any solution you find and use in HTML(5) and JavaScript is a hack in one way or another to make things work (for all different browsers).

To abuse the “class” attribute on the HTML elements for resolve definately is a dirty hack. It is also one of the most simple and pure ways to retrieve collections of elements from your DOM without adding yet another chunk of code to your project.

Cleaning up the dirt a bit

In my example I apply the ID of the object on each and every element that represents the Object in HTML.

You can reduce this by only adding the ID to the class of the HTML element that contains those elements. (Like the form or the enclosing DIV around the actual representative.)

Advertisements