HOW TO DO DATA BINDING IN PURE JAVASCRIPT?

How to do data binding in JavaScript?

How to do data binding in JavaScript?
Data binding plays an imperative role in today’s modern applications. It would be absurd if you want to write an application without it. A related to data binding is one of the most fundamental architectural patterns that have birth dozens of progeny, the Model-View-Controller (MVC) pattern.

In this article, we are going to describe how effectively you can do data binding in JavaScript?

One-way data binding

As a language, JavaScript is evolving day by day and that’s why the need for frameworks has been significantly mitigated. Data binding is not an intricated thing rather it is a very facile subject. In data binding, you have a data model on one side and on the other side, you have an interface which is often called a view. The prime idea of data binding is that you want to bind some parts of data to something on the view so that when the data changes, the view also changes automatically. This is typical for read-only data.

One-way data binding though looks simpler, it is probably the harder one to implement. This is because it requires hooking into JavaScript getters and setters for properties. JavaScript has for a long time had Object.defineProperty. This particular function allows the developers to create custom getters and setters for an object. By using this function, they can even replace them for existing properties. Take a look at the following code which uses this method to change the getter and setter for a previously defined property.

function Binding(b) {
_this = this
this.element = b.element
this.value = b.object[b.property]
this.attribute = b.attribute
this.valueGetter = function(){
return _this.value;
}
this.valueSetter = function(val){
_this.value = val
_this.element[_this.attribute] = val
}

Object.defineProperty(b.object, b.property, {
get: this.valueGetter,
set: this.valueSetter
});
b.object[b.property] = this.value;

this.element[this.attribute] = this.value

}

If you can check the code, then you must understand that the code is creating a shadow property to store the value in the Binding object and the using defineProperty to set the getter and setter for the property. Now, whenever the property is set with the equals sign (=), it will call the setter function. The function will set the property as well as the DOM element to the value.

Limited Two-way binding

In two-way data binding, changes are made on the view or model. The same basic code can be used to setup a two-way binding.

java script binding

In order to get DOM feedback, at first, the binding needs an event to listen for. You need to carefully call ‘addEventListener’. This adds an event listener to the element that was passed in. Whenever the event is called, the event handler will set the shadow copy of the object value that is data bound to the element. In two-way data binding, changes that you have made to the model will also update the DOM element.

function Binding(b) {
_this = this
this.element = b.element
this.value = b.object[b.property]
this.attribute = b.attribute
this.valueGetter = function(){
return _this.value;
}
this.valueSetter = function(val){
_this.value = val
_this.element[_this.attribute] = val
}

if(b.event){
this.element.addEventListener(b.event, function(event){
_this.value = _this.element[_this.attribute]
})
}

Object.defineProperty(b.object, b.property, {
get: this.valueGetter,
set: this.valueSetter
});
b.object[b.property] = this.value;

this.element[this.attribute] = this.value
}

Though this code is useful, still it is limited because it only allows mostly a single one-way or two-way data binding for the element and property.

Better two-way data binding or data

This is a better and advanced approach two-way data binding which allows a property to be bound to one or more elements. This means that the data binding can update multiple elements on the DOM when the value is changed either when a DOM event is fired or the model changes.

Now, the addBinding function will allow new elements to be added to the binding with events. These are added to to the elementBindings array. The setter function now iterates over the elementBindings array and updates the property whenever a new value is set. You can even use it for one-way data bindings by simply omitting the event parameter when calling addBinding.

function Binding(b) {
_this = this
this.elementBindings = []
this.value = b.object[b.property]
this.valueGetter = function(){
return _this.value;
}
this.valueSetter = function(val){
_this.value = val
for (var i = 0; i < _this.elementBindings.length; i++) {
var binding=_this.elementBindings[i]
binding.element[binding.attribute] = val
}
}
this.addBinding = function(element, attribute, event){
var binding = {
element: element,
attribute: attribute
}
if (event){
element.addEventListener(event, function(event){
_this.valueSetter(element[attribute]);
})
binding.event = event
}
this.elementBindings.push(binding)
element[attribute] = _this.value
return _this
}

Object.defineProperty(b.object, b.property, {
get: this.valueGetter,
set: this.valueSetter
});

b.object[b.property] = this.value;
}

JavaScript data binding without a framework

You really don’t need a framework or fancy cutting-edge JavaScript features to do two-way data binding. This can be done via Observer pattern, but a complete implementation of that is a little chunky. So, if native getters/setters are out, the only mechanism that you have are accessors, Check the below code.

var n = 5;
function getN() { return n; }
function setN(newN) { n = newN; }

console.log(getN()); // 5
setN(10);
console.log(getN()); // 10
If you find this code boring, then you can rearrange it to a single function.
var _n = 5;
function n(n) {
if (arguments.length) _n = n;
return _n;
}
console.log(n()); // 5
n(10);
console.log(n()); // 10
Now, you want to notify stuff when the value changes, then your code should be like this
var _n = 5, _nListeners = [];
function n(n) {
if (arguments.length && n !== _n) {
_n = n;
_nListeners.forEach(function(listener) { listener(n); });
}
return _n;
}
n.subscribe = function(listener) { _nListeners.push(listener); }

console.log(n()); // 5
n.subscribe(function(newN) { console.log(newN); });
n(10); // logs 10
n(10); // no output, value didn’t change.
This could be a daunting job if you need to do it in a large volume, now, modify it to a neat little generator function.
function observable(value) {
var listeners = [];

function notify(newValue) {
listeners.forEach(function(listener){ listener(newValue); });
}
function accessor(newValue) {
if (arguments.length && newValue !== value) {
value = newValue;
notify(newValue);
}
return value;
}
accessor.subscribe = function(listener) { listeners.push(listener); };

return accessor;
}
var n = observable(5);
n.subscribe(function(newN) { console.log(newN); });
n(10); // logs 10

Now, by using observable(), you can have as many little pre-packaged observable values as you want.
The next, you need to combine them. Your code should be like this,
var a = observable(3), b = observable(2);

var c = observable(a() + b());

a.subscribe(function(){ c(a() + b()); });
b.subscribe(function(){ c(a() + b()); });

console.log(c()); // 5
a(10);
console.log(c()); // 12
b(7);
console.log(c()); // 17
There are a lot of repetition, you can fix it by puling out the functions.
var a = observable(3), b = observable(2);

function calculation() { return a() + b(); }

var c = observable(calculation());

function listener() { c(calculation()); }
a.subscribe(listener);
b.subscribe(listener);

Now, you need a way to calculate the value of the observable, and need to know the observables that participate in that calculation. You will call this variation on an observable a computed value:

function computed(calculation, dependencies) {
// start with the initial value
var value = observable(calculation());

// register a listener for each dependency, that updates the value
function listener() { value(calculation()); }
dependencies.forEach(function(dependency) {
dependency.subscribe(listener);
});

// now, wrap the value so that users of computed() can’t manually update the value
function getter() { return value(); }
getter.subscribe = value.subscribe;

return getter;
}
Data binding
Now, you need to keep focus on Data binding. Let’s discuss it with examples. Let’s continue with the adding example, but now let’s represent it with text boxes:

+

=

Now, you need to add observable into the boxes which is a very daunting task.
var aText = document.getElementById(‘a-text’);
aText.value = a();
a.subscribe(function(_a){ aText.value = _a; });

var bText = document.getElementById(‘b-text’);
bText.value = b();
b.subscribe(function(_b){ bText.value = _b; });

var cText = document.getElementById(‘c-text’);
cText.value = c();
c.subscribe(function(_c){ cText.value = _c; });
Again, there is some repetition, so you need to clean it up.
function bindValue(input, observable) {
input.value = observable();
observable.subscribe(function(){ input.value = observable(); });
}

bindValue(aText, a);
bindValue(bText, b);
bindValue(cText, c);

Now, you need to update the values when the text boxes change. That’s pretty easy too, actually. All you need to do is listen to events on the input, and update the observable accordingly.

function bindValue(input, observable) {
input.value = observable();
observable.subscribe(function(){ input.value = observable(); });

input.addEventListener(‘input’, function() {
observable(input.value);
});
}
Now, whenever the textbox value changes, you will update the observable, and when the observable changes, you update the textbox. But this will actually only in theory. You need to make a slight adjustment to our bindValue. Let’s do a little sniffing to figure out what you want to do.
function bindValue(input, observable) {
var initial = observable();
input.value = initial;
observable.subscribe(function(){ input.value = observable(); });

var converter = function(v) { return v; };
if (typeof initial == ‘number’) {
converter = function(n){ return isNaN(n = parseFloat(n)) ? 0 : n; };
}

input.addEventListener(‘input’, function() {
observable(converter(input.value));
});
}

Now, if the initial value of the observable is a number, you try to interpret further values of the input as a number as well.
In this way, you will be able to implement a proof-of-concept two-way data-binding example using only vanilla JavaScript which is compatible for all the devices.

Hope, now you have an adequate knowledge of data binding and how to do data binding in JavaScript.

SUBSCRIBE NOW

Get actionable content delivered to your inbox every week.

Leave a Reply

Your email address will not be published. Required fields are marked *