Sunday, April 6, 2014

Rebinding a knockout view model

As you might have noticed reading my last two posts, I have been doing a bit of front-end work using knockout.js. Here is something that had me scratching my head for a little while..

In one of our pages we're subscribing to a specific event. As soon as that event arrives, we need to reinitialize the model that is bound to our container element. Going through snippets earlier, I remembered seeing the cleanNode function being used a few times - which I thought would remove all knockout data and event handlers from an element. I used this function to clean the element the view model was bound to, for then to reapply the bindings to that same element.

This seemed to work fine, until I used a foreach binding. If you look at the snippet below, what is the result you would expect?
<div id="books">
    <ul data-bind="foreach: booksImReading">
        <li data-bind="text: name"></li>
    </ul>
</div>

var bookModel = {
    booksImReading: [
        { name: "Effective Akka" }, 
        { name: "Node.js the Right Way" }]
};
                         
ko.applyBindings(bookModel, el);

var bookModel2 = {
    booksImReading: [
        { name: "SQL Performance Explained" },
        { name: "Code Connected" }]
};

ko.cleanNode(books);
ko.applyBindings(bookModel2, books);
Two list-items? One for "SQL Performance Explained" and one for "Code Connected"? That's what I would expect too. The actual result shows two list-items for "SQL Performance Explained" and two for "Code Connected" - four in total. The cleanNode function is apparently not cleaning the foreach binding completely.

Looking for documentation on the cleanNode function, I couldn't find any. What I did find was a year old Stackoverflow answer advising against using this function - since it's intended for internal use only.

I ended up making the book model itself an observable. The element is now being bound to a parent model that contains my original book model as an observable. When the event arrives now, I create a new book model and set it to that observable property. This results in my list being rerendered with just two items - like expected.
<div id="books">
    <ul data-bind="foreach: bookModel().booksImReading">
        <li data-bind="text: name"></li>
    </ul>
</div>

var page = {
    bookModel : ko.observable({
        booksImReading: [
            { name: "Effective Akka" }, 
            { name: "Node.js the Right Way" }]
    })
};
                          
ko.applyBindings(page, el);

page.bookModel({
    booksImReading: [
        { name: "SQL Performance Explained" },
        { name: "Code Connected" }]
});
Don't use the cleanNode function to rebind a model - instead make the model an observable too.

Sunday, March 23, 2014

Sending commands from a knockout.js view model

While I got to use angular.js for a good while last year, I found myself returning to knockout.js for the current application I'm working on. Where angular.js is a heavy, intrusive, opinionated, but also very complete framework, knockout.js is a small and lightweight library giving you not much more than a dynamic model binder. So instead of blindly following the angular-way, I'll have to introduce my own set of abstractions and plumbing again; I assume that I'll end up with a lot less.

Let's say that I have a view model for making a deposit.
var DepositViewModel = function() {
    var self = this;

    self.account = ko.observable('');
    self.amount = ko.observable(0);

    self.depositEnabled = ko.computed(function() {
        return self.account() !== '' && self.amount() > 0;
    });
    
    self.deposit = function() {
        if (!self.depositEnabled()) {
            throw new Error('Deposit should be enabled.');
        }

        $.ajax({ 
            url: '/Commands/Deposit', 
            data: { account: self.account(), amount: self.amount() }, 
            success: function() { self.amount(0); }
            type: 'POST', 
            dataType: 'json' 
        });
    };
};

ko.applyBindings(new DepositViewModel());
Writing a test for this, it was obvious that I couldn't have my deposit function make requests directly. An abstraction that has served me well in the past, is a command executor. 
CommandExecutor = function() {
    this.execute = function(command, success) { };
};
We can have an implementation that handles each command individually, or we can have it send requests to our server by convention. The implementation below assumes that the name of our command has a corresponding endpoint on the server. 
CommandExecutor = function() {

    this.execute = function(command, success) {

        if (console) {
            console.log('Executing command..');
            console.log(command);
        }

        $.ajax({ 
            url: '/Commands/' + command.name, data: command.data, 
            success: success
            type: 'POST', dataType: 'json' 
        });
    };
};
While angular.js has dependency management built in, we can get away by injecting dependencies manually and a bit of bootstrapping - it's not that I often have large dependency graphs in the browser, or that I care much about the life cycles of my components.
var DepositViewModel = function(dependencies) {
    var self = this;

    self.account = ko.observable('');
    self.amount = ko.observable(0);

    self.depositEnabled = ko.computed(function() {
        return self.account() !== '' && self.amount() > 0;
    });
    
    self.deposit = function() {
        if (!self.depositEnabled()) {
            throw new Error('Deposit should be enabled.');
        }

        var command = { 
            name: 'Deposit', 
            data: { account: self.account(), amount: self.amount() } };
        var callback = function() { self.amount(0); };
        dependencies.commandExecutor.execute(command, callback);
    };
};

ko.applyBindings(new DepositViewModel({ commandExecutor: new CommandExecutor() }));
See, very little magic required.

Writing a test, we now only need to replace the command executor with an implementation that will record commands instead of actually sending them to the server.
var CommandExecutorMock = function () {

    var commands = [];

    this.execute = function (command, success) {
        commands.push(command);
        success();
    };
    this.verifyCommandWasExecuted = function(command) {
        for (var i = 0; i < commands.length; i++) {
            if (JSON.stringify(commands[i]) === JSON.stringify(command)) {
                return true;                        
            }
        }
        return false;
    };

};

describe("When a deposit is invoked", function () {
    var commandExecutor = new CommandExecutorMock();
    
    var model = new DepositViewModel({ commandExecutor: commandExecutor });
    model.account('MyAccount');
    model.amount(100);
    model.deposit();

    it("a deposit command is sent.", function() {
        var command = {
            name: 'Deposit', 
            data: { account: 'MyAccount', amount: 100 }
        };

        expect(commandExecutor.verifyCommandWasExecuted(command)).toBe(true);
    });  
});
I did something similar for queries, and ended up with not that much code, that didn't even take that long to write. I'm curious to see how this application will evolve.

Sunday, March 16, 2014

Building a live dashboard with some knockout

Last week, we added a dashboard to our back office application that shows some actionable data about what's going on in our system. Although we have infrastructure in place to push changes to the browser, it seemed more reasonable to have the browser fetch fresh data every few minutes.

We split the dashboard up in a few functional cohesive widgets. On the server, we built a view-optimized read model for each widget. On the client, we wrote a generic view model that would fetch the raw read models periodically.
var ajaxWidgetModel = function (options) {
    var self = this;

    self.data = ko.observable();
    self.tick = function () {
        $.get(options.url, function (data) {
            self.data(ko.mapping.fromJS(data));
        });
    };

    self.tick();
    setInterval(self.tick, options.interval);
};
We then used knockout.js to bind the view models to the widgets.
ko.applyBindings(
    new ajaxWidgetModel({ url: "/api/dashboard/tickets", interval: 30000 }), 
    document.getElementById('widget_tickets'));

<div class="widget-title">
    <h5>Tickets</h5>
</div>
<div class="widget-content" id="widget_tickets" data-bind="with: data">
    <table class="table">
        ...
    </table>
</div>
The with data-binding ensures that the content container only gets shown when the read model data has been fetched from the server.

Building dumb view-optimized read models on the server, binding them to a widget with one line of code, and some templating, allowed us to quickly build a live dashboard in a straightforward fashion.