Implement Two-way Data Binding in JavaScript

One of the fundamental features of modern JavaScript libraries and frameworks is data binding. The data binding features offered by them allows you to keep model and view in sync. Did you ever wonder how something similar can be implemented in plain JavaScript? If so this article shows you one such simple yet working implementation. Although simple implementation as discussed in this article is not intended to substitute what is offered by these libraries and frameworks, it will certainly give you a glimpse of how data binding might be implemented if needed.

Data binding with an example

Ok. Let's see what we are going to develop. Have a look at the following figure :

The above figure shows two textboxes and one <div> element displaying a message - Hello World!. This message is actually comes from the Message property of a JavaScript object. Whenever you change the value in the textboxes our data binding implementation pushes the changes to the underlying object. On the same lines whenever you change the Message property the changes are reflected on the user interface. This way the Message property and all the three UI elements are kept in sync.

The HTML markup

The above page uses the following HTML markup:

<input type=text id="textBox1">
<br /><br />
<input type=text id="textBox2">
<br /><br />
<div id="divMessage"></div>

The page consists of two textboxes with IDs textBox1 and textBox2 respectively. There is a <div> with ID of divMessage. You can place this markup either in an HTML file or inside an ASP.NET Core view file.

The JavaScript code that does the data binding

Before we go ahead and demystify the code that implements the data binding, let's see the usage of what we are going to develop. That way you will get better idea of how the feature is being implemented. Consider the following JavaScript code :

var obj = {};
obj.Message = "Hello World!";

var dataBinder = new DataBinder({
    "model": obj,
    "property": "Message"
});

dataBinder.addDataBinding("textBox1", "value", "keyup");
dataBinder.addDataBinding("textBox2", "value", "keyup");
dataBinder.addDataBinding("divMessage", "innerHTML");

obj.Message = "Hello Universe!";

The above JavaScript code can be placed inside a <script> block at the bottom of the HTML file. Let's see what this code is doing.

The code creates a new JavaScript object - obj - and sets its Message property to "Hello World!".

It then proceeds to create a new DataBinder object. What's DataBinder? It's a JavaScript function we will create later. The DataBinder function is the heart of our data binding implementation. If you see the syntax, we are creating a function object using the new keyword. We also pass another object literal to the constructor that contains some settings.

The settings object passed in the constructor has two properties :

  • model : holds the object on which data binding is to be implemented.
  • property : holds the name of the object's property that will participate in the data binding.

The next three lines define the data bindings between the model object and the UI elements. The DataBinder object exposes a method - addDataBinding() - that takes three parameters :

  • ID of the UI element that is participating in the data binding.
  • DOM property of the UI element that will participate in the data binding. For textboxes, we want to data bind value property whereas for the <div> we want to data bind the innerHTML property.
  • The third optional parameter is the name of an event that will trigger synchronization between model data and UI. It is applicable to textboxes since they are capable of changing their value when user enters data. We use keyup event for this purpose. On the other hand <div> won't have this parameter for obvious reason.

Finally, we set the Message property of the model object to - Hello Universe! (remember that the default value is - Hello World!). This is just to check whether our data binding works as expected when the page loads in the browser.

Implementation of DataBinder

Add a new JavaScript file under wwwroot folder and write the skeleton of DataBinder function as shown below :

function DataBinder(settings) {
   ...
}

The DataBinder function takes a settings object as a parameter.

Now, let's complete this function step-by-step.

Add the following code inside the DataBinder() function :

 _this = this;
this.dataBindings = [];
this.value = settings.model[settings.property];

The above stores the "this" reference in a local variable called _this. We do this so that _this can be used safely inside other code blocks of DataBinder(). Then we declare a dataBindings array. The dataBindings array holds a list of binding objects. A binding object is basically a pair of an HTML element and its DOM property that is to be data bound. The dataBindings array is populated by the addDataBinding() function discussed later.

The code then sets the value member of DataBinder to the value of model's property. Notice how the model property value is obtained using the dictionary syntax. In our specific example, model will the obj and property will be Message.

Next, add the following fragment of code below the above lines :

this.propertyGet = function () {
    return _this.value;
};


this.propertySet = function (value) {
    _this.value = value;
    for (var i = 0; i < _this.dataBindings.length; i++) {
        var binding = _this.dataBindings[i];
        binding.element[binding.attribute] = value;
    }
};


Object.defineProperty(settings.model, settings.property, {
    get: this.propertyGet,
    set: this.propertySet
});

This is possibly the most important part of DataBinder function. Here, we define two functions namely propertyGet() and propertySet(). The propertyGet() function returns the value to the caller. The propertySet() function sets the value and also sets the DOM property of all the HTML elements participating in the data binding. This is done by iterating through the dataBindings array and setting the DOM element's property to value.

JavaScript allows you to specify custom getter and setter functions that are used when an object's property is accessed. This is done using the defineProperty() method shown next.

The Object.defineProperty() takes three parameters:

  • The object whose properties need custom getter and setters - obj in this case.
  • The object's property whose access is granted through the custom getter and setter - Message in this case.
  • The custom set and set functions wrapped in an object literal.

Now we will add the addDataBinding() method as shown below.

this.addDataBinding = function (element, property, event) {

    var domElement = document.getElementById(element);

    var binding = {
        element: domElement,
        property: property
    };

    if (event) {
        domElement.addEventListener(event, function () {
            _this.propertySet(domElement[property]);
        });
        binding.event = event;
    }
    this.dataBindings.push(binding);
    domElement[property] = _this.value;
    return _this;
};

The addDataBinding() method takes three parameters (see previous section). Inside, we grab the target HTML element using getElementById() and store its reference in domElement variable.

The code then creates a binding definition object containing two properties namely element and property. The binding definition object can also have optional event property. The event property is assigned only for those elements where an HTML event (such as keyup) is specified while adding a data binding. That's why we check the event parameter and if present wire an event handler using the addEventListener() method. The event handler simply calls the custom property setter by passing the DOM element's value. This way model's property is synchronized with the UI element's value.

The binding definition object is then pushed to the dataBindings array using the push() method. Finally, the DOM element's property is synchronized with the value member. We need to do this so that when a new binding is added the UI element will reflect the model's property value.

Finally, complete the DataBinder with this line:

 settings.model[settings.property] = this.value;

Ok. So far so good.

Now add a <script> reference to the DataBinder.js file page you just created in the Index.

 <script src="DataBinder.js"></script>

Run the Index page and try changing values in the textboxes. If all goes well you should see all the three DOM elements synchronizing their values. The following figure shows a sample run of the page:

That's it for now! Keep coding!!


Bipin Joshi is a software consultant, trainer, author, yoga mentor, and spiritual guide having 24+ years of experience in software development, consulting, and training. He conducts instructor-led online training courses in ASP.NET Core, ASP.NET MVC, and Design Patterns for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get article updates : Facebook  Twitter  LinkedIn

Posted On : 31 December 2018


Tags : ASP.NET ASP.NET Core MVC .NET Framework C#


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates