Please treat this question as strictly educational. I'm still interested in hearing new answers and ideas to implement this
How would I implement bi-directional data-binding with JavaScript?
By data binding to the DOM I mean for example, having a JavaScript object a
with a property b
. Then having an <input>
DOM element (for example), when the DOM element changes, a
changes and vice versa (that is, I mean bidirectional data binding).
Here is a diagram from AngularJS on what this looks like:
So basically I have JavaScript similar to:
var a = {b:3};
Then an input (or other form) element like:
<input type='text' value=''>
I'd like the input's value to be a.b
's value (for example), and when the input text changes, I'd like a.b
to change too. When a.b
changes in JavaScript, the input changes.
What are some basic techniques to accomplish this in plain JavaScript?
In specific, I'd like a good answer to refer to:
I'm a big fan of Mustache so I tried using it for templating. However, I ran into issues when trying to perform the data binding itself since Mustache processes HTML as a string so after I get its result I have no reference to where the objects in my viewmodel are. The only workaround I could think for this was modifying the HTML string (or created DOM tree) itself with attributes. I don't mind using a different templating engine.
Basically, I got a strong feeling that I was complicating the issue at hand and there is a simple solution.
Note: Please do not provide answers that use external libraries, especially ones that are thousands of lines of code. I've used (and like!) AngularJS and KnockoutJS. I really don't want answers in the form 'use framework x'. Optimally, I'd like a future reader who doesn't know how to use many frameworks to grasp how to implement bi-directional data-binding herself. I do not expect a complete answer, but one that gets the idea across.
- How would binding work for objects?
- How listening to change in the form might work?
I suppose there are other techniques, but ultimately I'd have an object that holds reference to a related DOM element, and provides an interface that coordinates updates to its own data and its related element.
The .addEventListener()
provides a very nice interface for this. You can give it an object that implements the eventListener
interface, and it'll invoke its handlers with that object as the this
value.
This gives you automatic access to both the element and its related data.
Prototypal inheritance is a nice way to implement this, though not required of course. First you'd create a constructor that receives your element and some initial data.
function MyCtor(element, data) {
this.data = data;
this.element = element;
element.value = data;
element.addEventListener("change", this, false);
}
So here the constructor stores the element and data on properties of the new object. It also binds a change
event to the given element
. The interesting thing is that it passes the new object instead of a function as the second argument. But this alone won't work.
eventListener
interfaceTo make this work, your object needs to implement the eventListener
interface. All that's needed to accomplish this is to give the object a handleEvent()
method.
That's where the inheritance comes in.
MyCtor.prototype.handleEvent = function(event) {
switch (event.type) {
case "change": this.change(this.element.value);
}
};
MyCtor.prototype.change = function(value) {
this.data = value;
this.element.value = value;
};
There are many different ways in which this could be structured, but for your example of coordinating updates, I decided to make the change()
method only accept a value, and have the handleEvent
pass that value instead of the event object. This way the change()
can be invoked without an event as well.
So now, when the change
event happens, it'll update both the element and the .data
property. And the same will happen when you call .change()
in your JavaScript program.
Now you'd just create the new object, and let it perform updates. Updates in JS code will appear on the input, and change events on the input will be visible to the JS code.
var obj = new MyCtor(document.getElementById("foo"), "20");
// simulate some JS based changes.
var i = 0;
setInterval(function() {
obj.change(parseInt(obj.element.value) + ++i);
}, 3000);