Understanding Two-Way Data Binding

Probably you have experienced tediousness of writing code for marshaling data in and out of web UI. There weren’t too many options to avoid it until the client-side MV* frameworks appeared on the horizons in last couple years. Two-way data binding eliminates the bulk of boilerplate code you would have to write otherwise.

It simplifies development by raising the level of abstraction and frees you up to focus on more important aspects of your application. Two-way data binding employs Observer design pattern, and it notifies subscribers when the observed object changes. Every MV* frameworks have slightly different take on how to implement it. One of the approaches for change detection is to use special constructs for your model properties so that it could raise change notifications. Many people feel the need for extending your model corrupts it and mixes concerns. KnockoutJS uses this approach whereas AngularJS uses a different approach and lets you work with plain JavaScript objects.

I wanted to take a stab at developing very basic two-way binding mechanism for the sake of better understanding. Following screenshot shows this very basic two-way binding in action.

{% img /images/databinding/basic-binding.png %}

Following is how the UI is wired up

<label for="message">Say Something</label>
<input id="message" type="text" data-bind="value: message" class="span10"/>
<h2 data-bind="value: message"></h2>
myViewModel = { message: Binder.observable("Hello there...")};

$(document).ready(function () {
    'use strict';
    Binder.applyBinding(myViewModel);
});

Following is the implementation of the Binder

Binder = (function () {
    'use strict';
    var Binder = {};

    Binder.applyBinding = function (viewmodel) {
        $('[data-bind]').each(function (index, element) {
            var field, observable, binding = $(element).data('bind'),
                $ele = $(element);

            field = binding.split(':')[1].trim();
            observable = viewmodel[field];

            if ($ele.is('input') && $ele.attr('type') === 'text') {
                $ele.val(observable());

                $ele.on('input', function () {
                    observable($(this).val());
                });

                observable.listener = function (updatedValue) {
                    $ele.val(updatedValue);
                };
            } else {
                $ele.text(observable());

                observable.listener = function (updatedValue) {
                    $ele.text(updatedValue);
                };
            }
        });
    };