MVVM: The KnockoutJS Redemption

Also available in portuguese

In this article I will show you how to use KnockoutJS in a simple way writing javascript in Revealing Module Pattern as an alternative to AngularJS.

KnockoutJS is javascript plugin with Model-View-View-Model (MVVM) pattern. When your data model’s state changes, your UI updates automatically. Like AngularJS it’s provides a full control for a view and his model.

MVVM: The KnockoutJS Redemption
 

What is the article for?

Everyone out there talking about AngularJS or ReactJS and they forget about KnockoutJS and how it can help you. So, this is the KnokoutJS redemption!

What is the goal?

1 – Use KnockoutJS to handle the view stuff;
2 – Write javascript code in Revealing Module Pattern;
3 – Create a feature to insert, edit and delete data;
4 – Demonstrate how KnockoutJS works using mapping, computed variables and computed methods.

What is the demo application?

I have a page that contains a list of people. When I click a person I can delete or edit that person. I also want to add a new person. Very simple.

MVVM: The KnockoutJS redemption

What we need for this article?

1 – KnockoutJS javascript plugin;
2 – KnockoutJS javascript mapping plugin;
3 – jQuery jsvascript plugin;
4 – Our person viewmodel in javascript;
5 – An index.html for our simple view;

index.html

<!DOCTYPE html>
<html>
<head>
    <title>MVVM: The KnockoutJS redemption</title>
	<meta charset="utf-8" />
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/knockout-3.2.0.js"></script>
    <script src="Scripts/knockout.mapping-latest.js"></script>
    <script src="Scripts/view-models/person.js?vswfsd"></script>
</head>
<body>
<div id="person">
<table border="1">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Gender</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: $root.list">
<tr>
<td><span data-bind="html: $index() + 1"></span></td>
<td>
                        <span data-bind="visible: !$data.IsEditing(), html: $data.Name"></span>
                        <input type="text" data-bind="visible: $data.IsEditing, value: $data.Name" /></td>
<td>
                        <span data-bind="visible: !$data.IsEditing(),html: $root.getGenderName($data)"></span>
                        <select data-bind="visible: $data.IsEditing, options: $root.genders, optionsValue: 'Id', optionsText: 'Name', value: $data.Gender" /></td>
<td>
                        <input type="button" data-bind="visible: !$data.IsEditing(), click: $root.onEdit, enable: !$root.isEditing()" value="edit" />
                        <input type="button" data-bind="visible: $data.IsEditing, click: $root.onSave" value="save" />
                        <input type="button" data-bind="click: $root.onDelete, enable: $data.IsEditing() || !$root.isEditing()" value="delete" /></td>
</tr>
</tbody>
</table>
<input type="button" data-bind="click: $root.onInsert, enable: !$root.isEditing()" value="insert" /></div>
<script type="text/javascript">
        $(document).ready(function () {
            // initial data
            personViewModel.init({ List: [{ Name: 'Jack', Gender: 0, IsEditing: false }, { Name: 'Charlie', Gender: 1, IsEditing: false }, { Name: 'Hugo', Gender: 0, IsEditing: false }] });
        });
    </script>
</body>
</html>

It is a simple html with tables and other elements. The knockoutJS initialization will be at:

personViewModel.init({ List: [{ Name: 'Jack', Gender: 0, IsEditing: false }, { Name: 'Charlie', Gender: 1, IsEditing: false }, { Name: 'Hugo', Gender: 0, IsEditing: false }] });

All KnockoutJS MVVM are defined in “data-bind” attributes on elements, for instance:

<select data-bind="visible: $data.IsEditing, options: $root.genders, optionsValue: 'Id', optionsText: 'Name', value: $data.Gender" />

It means KnockoutJS will fill that select element with options and those options will came from “$root.genders” array property. Also, select element will be visible only if “$data.IsEditing” properfy of an person is true.

In KnockoutJS, we use “$root” to explicit some method or property at root level of viewModel. We use “$data” when a property or a method inside an item in a foreach loop.

The javascript “personViewModel” in Revealing Module Pattern:

Scripts/view-models/person.js

/// <reference path="../knockout-3.2.0.js" />
/// <reference path="../knockout.mapping-latest.js" />
/// <reference path="../jquery-1.10.2.js" />

var personViewModel = function () {

    var _vm = null,

    map = function (obj) {
        return ko.mapping.fromJS(obj);
    },

    createComputed = function () {

        _vm.isEditing = ko.computed(function () {
            return $.grep(_vm.list(), function (n) {
                return n.IsEditing() === true;
            }).length > 0;
        }, _vm);

    },

    init = function (model) {
        _vm = {
            list: map(model.List),
            genders: [
                { Id: 0, Name: 'Select...' },
                { Id: 1, Name: 'Masc' },
                { Id: 2, Name: 'Fem' }
            ],
            test: ko.observable('initial string value'),
            onEdit: function (person) {
                person.IsEditing(true);
            },
            onSave: function (person) {
                person.IsEditing(false);
            },
            onDelete: function (person) {
                if (confirm('Are you sure?')) {
                    var index = _vm.list.indexOf(person);
                    _vm.list.splice(index, 1);
                }
            },
            onInsert: function () {
                _vm.list.push(map({ Name: 'new person', Gender: 0, IsEditing: true }));
            },
            getGenderName: function (person) {
                if (person.Gender() === 0) {
                    return '-';
                }

                return $.grep(_vm.genders, function (n) {
                    return n.Id === person.Gender();
                })[0].Name;
            }
        };

        createComputed();

        var ctx = $('#person').get(0);
        ko.applyBindings(_vm, ctx);
    }

    return {
        init: init
    }

}();

The “personViewModel” reveals just only one method: the “init” method. You can see there are two others methods: “createComputed” and “map“. They are NOT exposed in the Revealing Module Pattern, they are private.

For that example, KnockoutJS will handle MVVM inside a DIV with id=”person” and all observable properties inside “_vm” variable. The configuration is:

var ctx = $('#person').get(0);
ko.applyBindings(_vm, ctx);

All methods and properties inside “_vm” variable are called “ROOT level” that you can use “$root” syntax in the view.

It means all changes in view will propagate to the javascript and all changes made in javascript will propagate to the view. If something it’s outside the “#person” DIV, KnockoutJS will not handle.

Be aware! Only the “genders” array in “_vm” are NOT handled by KnockoutJS. It means if you try to insert a new gender or change any existing gender, KnockoutJS will not update the view. To configure KnockoutJS to handle an object array will need to use KnockoutJS mapping plugin. I used that for list of people “_vm.list“.

If you need to GET a property value handled by Knockout will need to call with brackets:

var value = person.IsEditing();

If you need to SET a value for a property handled by KnockoutJS will need set it in brackets:

person.IsEditing(true);

This is it. I hope it helped.

MVVM: The KnockoutJS redemption

MVVM KnockoutJS: Questions, suggestions and feedbacks will be appreciated. Good luck!

Do complete download of the source code on github.

Try demo online of that application on codefinal.
About the Author:
He works as a solution architect and developer, has more than 16 years of experience in software development on several platforms and more than 14 years only for the insurance market.