Javascript Data Binding

Simpli5 has been coming along nicely as I’ve been able to put time into it. I’m very excited to announce data-binding.

Data Binding

Data binding is a technique we have and use in Flash quite a bit that allows one property to stay in sync with another. If obj.x is bound to obj.y then whenever obj.x is changed, obj.y will automatically update to the same value. I’ve built my own data binding frameworks in ActionScript 3 and I wanted it for my Javascript work as well.

Since Simpli5 has a base-line of HTML5, I was easily able to create data binding in Javascript using implicit getters and setters. Any property on an object can be bound. My binding supports one-way, two-way, and bind-setters, and it allows deep binding.

One-way binding propagates the changes of the source object’s property to the target object’s property. Whenever the source’s property changes, the target’s property updates. If the target’s property is changed however, nothing will happen to the source.

Two-way binding keeps the properties of the source and target in sync. When first bound, the source’s property will be the value used, but if either the source’s property is changed or the target’s property is changed the other will update to stay in sync.

Deep binding is being able to bind the property of a property of the source. As an example, obj.x would be shallow binding where obj.x.y.z would be deep binding.

Binding Usage

The following shows how binding can be used.

var obj1 = { name: 'Bob' };
var obj2 = { name: 'Henry' };

Bind.property(obj1, 'name', obj2, 'name');

alert(obj2.name); // will be Bob
obj1.name = 'Patricia'; // the binding makes obj2.name update as well
alert(obj2.name); // will be Patricia

var obj1 = { name: 'Bob', address: { zip: 12345 } };
var obj2 = { name: 'Henry', address: { zip: 56789 } };

Bind.property(obj1, 'address.zip', obj2, 'address.zip', true); // the last param (true) means a two way bind

alert(obj2.address.zip); // 12345

obj2.address.zip = 'unknown';
alert(obj1.address.zip); // will be 'unknown' because we are 2-way binding
obj1.address.zip = 12345;
alert(obj2.address.zip); // will be 12345 again, same as obj1

Binding added to Template

I’ve added a new method to Template called createBound(). The Template.create() method will give you back an HTML element created from the template’s HTML, replacing anything in curly braces with the evaluated javascript equivalent. With Template.createBound() you get back an HTML element which continues to update its attributes or text content as properties in the data or element change. Perhaps the best way to explain is to show some code.

var template = new Template('<button><img src="{this.src}" alt="{this.label}"/> Say {this.label}</button>');
var btn = template.createBound();
alert(btn.outerHTML); // <button><img src="" alt=""/> Say 

btn.label = 'Hello World';
btn.src = 'images/hello_world.png';

alert(btn.outerHTML); // <button><img src="images/hello_world.png" alt="Hello World"/> Say Hello World</button>

I’ve made it the component creation method of course, so now the properties on your components can update the HTML directly. And it isn’t setting the innerHTML of the component, which would replace all the sub-elements and destroy any listeners that were on the old ones. It’s binding smartly so that attributes and text get updated carefully.

Limitations

There are properties on elements that binding will not work the way you’d expect. Things like element.id, element.firstChild, img.src, et cetera, which the browser treats in a special way but are not getters and setters (as far as the javascript can tell anyway), these cannot be bound to reliably. They cannot be the source of a bind, but they could be the target of a one-way (non-deep) bind just fine.

Testing

I’ve started adding unit tests to the framework, especially since data binding can be such a tricky thing. I’ve got tests around the binding and the templates right now and feel confident that they are working pretty well. I’m using js-test-driver. I’ve also added a maven build file for the tests and for compressing the library into a single file and a compressed file.

The Simpli5 project is being hosted on Github: http://github.com/jacwright/simpli5.

8 Responses to “Javascript Data Binding”

  1. Robert Penner Says:

    What’s the key in JavaScript to detecting when a property changes? Is __observers__ part of JS or your own thing?

  2. Jacob Wright Says:

    __observers__ is my own thing. First, if something is “listened” to the property is converted into a getter/setter that dispatches when the prop is changed. Then, the listeners are added to the __observers__ property.

    In Flash we have dictionaries that help us store things for an object without touching the object, but objects can’t be stored as the key in an object hash (converts to a string). In javascript we have to store the observers somewhere we can get to so that 1) it doesn’t get garbage collected and 2) for removing a bind.

    In AS2 Macromedia created their own event system in a similar way using a __listeners__ property (or something like that).

  3. Jacob Wright Says:

    To follow, the built-in methods to objects __lookupGetter__, __lookupSetter__, __defineGetter__, and __defineSetter__ are really what makes this possible. I’m able to create a getter/setter for a property or even store the old getter/setter if one existed and set it when my own setter is activated.

  4. Clay Auces Says:

    I am having problem with the first link. It gives a 404 error?
    Thanks

  5. Jacob Wright Says:

    I’m not having any issues. The links all seem to work fine. Try again?

  6. Donny V Says:

    Any way to not have to setup the Bind.property binds? Just have it find the properties that are named the same.

  7. JavaScript Data Binding Frameworks - Dan Wahlin's WebLog Says:

    […] Simpli5 JavaScript framework that provides support for two-way data binding. […]

  8. JavaScript Data Binding Frameworks - Dan Wahlin Says:

    […] Simpli5 JavaScript framework that provides support for two-way data binding. […]