Data Binding is one of the most important features of all the frameworks. It ensures that the data model and the views are in sync with each other. It is very fundamental feature of any MV* frameworks such that Model-View-Controller, Mode-View-ViewModel, and Model-View-Presenter, etc.
Popular JavaScript based frameworks such that React, Angular have their way to implement it. However, it is a good idea to know how a basic two-way data binding can be implemented using vanilla JavaScript. That would give you an overview of how much heavy lifting is done by the framework when you use features like data binding.
To get most of this post, you should have a basic understanding of following JavaScript features
- Creating an object as literal
- Adding a property with Object.defineProperty
- Using setter and getter
- forEach statement
You can refresh about JavaScript object creation in this video on geek97. Let us get started,
On the HTML, we have put one input text box and two span elements. Whenever the user enters a value in the text box, data in <span> element should get updated.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<body> | |
<h2>Two-way data binding</h2> | |
Name: <input data-geek97-bind="name" type="text"> | |
<span data-geek97-bind="name"> </span> | |
<span data-geek97-bind="name"> </span> | |
<script src="databinding.js"></script> | |
</body> |
We are going to write JavaScript for data binding in the databinding.js file, so we also added a reference to that in the HTML.
To start with databinding.js file contains an Immediately Invoked Function Expression (IIFE). We write all the codes inside this function,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (){ | |
console.log('Implementing two way data binding'); | |
})(); |
At this point, on launching the application, you should get below output. Right now, if you enter anything in the Name field nothing would be affected.
Note: If you are using Visual Studio Code, install the extension Live Server, and run the HTML in the browser, right click on the file and from the context menu select “Open with Live Server” to launch the application.
Our first task is to select all the elements which have [data-geek97-bind] attribute set, and also check for their type. Right now, as we are creating binding only for input type text, so we can make a selection of elements and type checking as shown below,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var elements = document.querySelectorAll('[data-geek97-bind]'); | |
var dbrepo = {}; | |
elements.forEach((element)=>{ | |
if(element.type === 'text'){ | |
let bindingProperty = element.getAttribute('data-geek97-bind'); | |
console.log(bindingProperty); | |
} | |
}); |
We have created an empty object called dbrepo, which would maintain the state between the data bound elements. The dbrepo object would make sure that all DOM elements with [data-geek97-bind] attribute set to value ‘name’ is in sync and have the same value.
To achieve that, what all we need to do is to add a property dynamically to the dbrepo object for each unique value of [data-geek97-bind] attribute in the DOM elements. For that, let us create a function called addToScope. In the function, we check whether a particular property exists or not, if not then using the Object.defineProperty() method adds a property to the dbrepo object.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function addToScope(){ | |
if(!dbrepo.hasOwnProperty(bindingProperty)){ | |
let value; | |
Object.defineProperty(dbrepo,bindingProperty,{ | |
configurable:true, | |
enumerable:true , | |
set: function(newvalue){ | |
value = newvalue; | |
}, | |
get:function(){ | |
return value; | |
} | |
}) | |
} | |
} |
Using the hasOwnProperty() method, determining whether property already exists or not, if not then add it using the Object.defineProperty() method. You can learn more about property descriptor and Object.defineProperty() here
We need to do some work in the setter function.
- First, find all the elements with data-geek97-bind attribute set to the same value.
- If the element type is text, set its value, otherwise set innerHtml of the element
Above two tasks can be performed in the setter function as below,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
set: function (newvalue) { | |
value = newvalue; | |
elements.forEach(e => { | |
if (e.getAttribute('data-geek97-bind') === bindingProperty) { | |
if (e.type === 'text') { | |
e.value = newvalue | |
} | |
else if (!e.type) { | |
e.innerHTML = newvalue | |
} | |
} | |
}) | |
}, |
Putting everything together, code to perform two-way data binding is as below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var elements = document.querySelectorAll('[data-geek97-bind]'); | |
var dbrepo = {}; | |
elements.forEach((element) => { | |
if (element.type === 'text') { | |
var bindingProperty = element.getAttribute('data-geek97-bind'); | |
addToScope(); | |
element.onkeyup = () => { | |
dbrepo[bindingProperty] = element.value; | |
} | |
} | |
function addToScope() { | |
if (!dbrepo.hasOwnProperty(bindingProperty)) { | |
let value; | |
Object.defineProperty(dbrepo, bindingProperty, { | |
configurable: true, | |
enumerable: true, | |
set: function (newvalue) { | |
value = newvalue; | |
elements.forEach(e => { | |
if (e.getAttribute('data-geek97-bind') === bindingProperty) { | |
if (e.type === 'text') { | |
e.value = newvalue | |
} | |
else if (!e.type) { | |
e.innerHTML = newvalue | |
} | |
} | |
}) | |
}, | |
get: function () { | |
return value; | |
} | |
}) | |
} | |
} | |
}); |
If you want to change the value in the code by just rewriting the value of the property in the dbrepo object,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
changeName = function () { | |
dbrepo.name = 'set by code '; | |
} |
At this point, on launching the application you should get below output. Right now, if you enter anything in the Name field, the other two spans would be affected.
Still, there is scope for much improvement in this code,
- Instead of rewriting the innerHtml, append the next text
- Extend it for other types than text.
I hope you find this post useful. Thanks for reading.
Leave a Reply