Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Pausing Notifications in KnockoutJS

| Comments

On the forums for Knockout, there have been several questions related to temporarily preventing the changes to an observable from being sent out to subscribers. Typically, someone wants to apply many changes to their view model, perhaps as the result of an update call to the server, without having subscriptions triggered until the very end.

This behavior is not currently built into Knockout, but it is still possible to achieve this in a fairly straightforward way. Suppose we have a simple view model that looks like:

1
2
3
4
5
6
7
8
9
var viewModel = {
    users: ko.observableArray(usersFromServer)
}
//just the active users
viewModel.activeUsers = ko.computed(function() {
    return ko.utils.arrayFilter(viewModel.users(), function(user) {
        return user.isActive();
    });
}, viewModel);

If we receive updates from the server that add/remove users and/or modify the status of each user, then the activeUsers computed observable will be re-evaluated on each and every one of those changes.

To add the ability to pause activeUsers from being re-evaluated on every change, we can create an augmented computed observable with this added functionality.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//wrapper for a computed observable that can pause its subscriptions
ko.pauseableComputed = function(evaluatorFunction, evaluatorFunctionTarget) {
    var _cachedValue = "";
    var _isPaused = ko.observable(false);

    //the computed observable that we will return
    var result = ko.computed(function() {
        if (!_isPaused()) {
            //call the actual function that was passed in
            return evaluatorFunction.call(evaluatorFunctionTarget);
        }
        return _cachedValue;
    }, evaluatorFunctionTarget);

    //keep track of our current value and set the pause flag to release our actual subscriptions
    result.pause = function() {
        _cachedValue = this();
        _isPaused(true);
    }.bind(result);

    //clear the cached value and allow our computed observable to be re-evaluated
    result.resume = function() {
        _cachedValue = "";
        _isPaused(false);
    }

    return result;
};

We can now replace our computed observable with a pauseableComputed:

1
2
3
4
5
viewModel.activeUsers = ko.pauseableComputed(function() {
    return ko.utils.arrayFilter(viewModel.users(), function(user) {
        return user.active();
    });
}, viewModel);

When we want to apply many updates at once, we can wrap our actual updates with the pause() and resume() calls.

1
2
3
4
5
6
7
//callback from AJAX call to retrieve updates
viewModel.onUpdateUsersSuccess = function() {
    this.activeUsers.pause();
    //add or remove users
    //update status flags for users
    this.activeUsers.resume();
}.bind(viewModel);

The key fact that makes this work is that dependency detection is performed each time that a computed observable is re-evaluated. This means that the actual dependencies for a computed observable can change over time. In our case, the dependencies will look like:

When we call pause(), the _isPaused flag is set, which forces our computed observable to be re-evaluated. Now, we release our subscriptions to the users array and to the isActive flag of each user, but we do keep a cached copy of our latest value to return while we are paused.

When we call resume(), the cached value is thrown away and our computed observable is re-evaluated, because our only current dependency, the _isPaused flag, has now changed.

From what I have seen, the most common scenario is that a computed observable depends on many observables that you want to update en masse before re-evaluating the dependentObservable. However, at the bottom of the code for this sample, there is also an implementation that supports pausing writeable computed observables and one for pausing an observableArray as well (would be the same pattern for an observable).

Here is a live sample:

Link to full sample on jsFiddle.net

Controlling How an Object Is Converted to JSON

| Comments

As a follow up to the post on Knockout utilities, I thought it might be useful to mention one less than obvious trick that you can use in conjunction with ko.toJSON.

When preparing your data to be sent back to the server, typically you will call ko.toJSON on your view model or at least part of your view model. This first calls ko.toJS to give you a clean copy of your objects with all observables unwrapped into plain properties. Then it converts this clean object into JSON using JSON.stringify, which is implemented natively by your browser (or with a library like json2.js for older browsers).

However, it is not uncommon for your objects to contain computed observables or other observables that aren’t meant to go back to the server. Maybe your server-side framework can’t handle the extra properties or you don’t want to wastefully send the extra bytes on the wire. In some cases you might even have a child with a reference to its parent, which creates a circular reference that causes problems in the conversion to JSON.

While you could walk your object tree and delete any unwanted properties, there is actually an easier way. JSON.stringify will check if each object has a toJSON method and execute it first before serializing the object to its JSON representation. The name of the function “toJSON” is a bit misleading, as the function should return an object that is ready to be serialized into JSON, not your own attempt at a JSON string representing the object.

Suppose that you are constructing a simple Person object that looks like:

1
2
3
4
5
6
7
function Person(first, last) {
    this.first = ko.observable(first);
    this.last = ko.observable(last);
    this.full = ko.dependentObservable(function() {
        return this.first() + " " + this.last();
    }, this);
}

When you call ko.toJSON on an object like this you will get:

1
{"first":"John","last":"Smith","full":"John Smith"}

Maybe your server-side code is not expecting the full property or you just want to avoid sending it to be efficient. To control which properties are actually serialized, you can implement a toJSON function for your object. It could look something like:

1
2
3
4
5
Person.prototype.toJSON = function() {
    var copy = ko.toJS(this); //easy way to get a clean copy
    delete copy.full; //remove an extra property
    return copy; //return the copy to be serialized
};

There are certainly many ways that you could implement this function, but the main objective is to return a version of the object that is ready to serialized. Now, your JSON would look like:

1
{"first":"John","last":"Smith"}

What if you would prefer to only send back the full name? Suppose that you have an array of Person objects and in JSON you want to convert it to an array of strings containing just the full names. In that case you could implement toJSON like:

1
2
3
Person.prototype.toJSON = function() {
   return ko.utils.unwrapObservable(this.full);
};

I used unwrapObservable, because if this happens to get called as part of ko.toJSON, then all of the observables would already be plain properties. This way we are flexible enough to handle it either way. If we had an array of Person objects, when converted to JSON they would look like:

1
["Ted Johnson","Jon Johnson","Rachel Johnson"]

Now, we have just an array of strings (no property names) to send to the server.

Here is a live sample:

Link to full sample on jsFiddle.net

Implementing the toJSON function for your objects can be a nice way to avoid manually mapping your view model. You can easily transform your data into whatever format is most suitable to be sent to your server.

Knockout 1.2.0 Now Available

| Comments

Today the 1.2.0 version of Knockout was released. Check out this postfor details on the changes that are included. I am very excited to not have to keep saying this feature was not in the 1.12 release, you will need the latest code! Lots of great improvements in 1.2 including serious performance fixes, new bindings (event, attr, and html), and my personal favorite new feature, writable computed observables. I think that this is a really solid release and I look forward to the future of this great library.

The 1.2.0 script is available here. If you are working in Visual Studio, then you can also get it via NuGet (Install-Package knockoutjs).

Event Delegation in KnockoutJS

| Comments

Update #2: Check out this post for a new look at this topic and take a look at the plugin here.

Update: In Knockout 2.0, it is now possible to use ko.dataFor and ko.contextFor instead of tmplItem and use this technique with native templates. Documentation here.

Prior to working with Knockout, I had recently grown fond of event delegation in JavaScript where an event listener is attached to a parent container and events from its children bubble up to this handler. I had primarily been using jQuery’s live() or its more precise cousin delegate() to handle this type of interaction in a fairly painless way.

There are several fairly obvious advantages to using event delegation in cases where you would be adding listeners for lots of events (like for every cell in a large grid):

  • New elements added dynamically to the container can be handled without any additional code. Elements can be removed without having to manage unbinding handlers.
  • Less overhead (memory, processing) than wiring up events to every element.
  • Gives you the flexibility to use one handler to respond to events at multiple levels or from different kinds of elements.

At first glance, it seemed like event delegation didn’t quite fit the Knockout model. You certainly could set a handler on a parent element using the click or event binding. This would allow you to respond to actions on the children, but you would have no connection to the actual data that was used to generate the child element. You could also just use something like jQuery’s live() or delegate() functionality, but you would not get the integration with your view model data that you are used to in the bindings.

tmplItem - a hidden gem in jQuery Templates

In Knockout, the majority of the child content is typically rendered through templates, using the jQuery Templates plug-in. One lesser publicized feature of the plug-in is the tmplItem function. For any element generated by a template, this function allows you to get back to the data that was used to render the template.

For example,

1
2
3
4
5
6
<div data-bind="template: { name: 'nameTmpl', data: person }"></div>

<script id="nameTmpl" type="text/html">

     <span id="name">${firstName} ${lastName}</span>
</script>
1
2
3
4
5
6
7
8
9
10
var viewModel = {
    person: {
        firstName: "Bob",
        lastName: "Smith"
    }
}

ko.applyBindings(viewModel);

var theData = $("#name").tmplItem().data;  //this returns our "person"

So, using the tmplItem function, we are actually able to retrieve some context from an element that was rendered by a template. This is the piece that we were missing to connect our delegated event with the model data related to our child element.

One note: if you are using {{each}} syntax, tmpItem() will still return the data that was the context of the entire template. You can use the template binding or the {{tmpl}} tag to help achieve the proper context for your elements.

Are there benefits to doing event delegation in Knockout?

Wiring up events for dynamically created elements is generally not a problem in Knockout, as we are usually generating the content in templates and can easily add our event handlers declaratively. The other benefits of event delegation still stand though, especially in the case that we have a need to wire up a large number of handlers on our page. I can also see one other small advantage: the first time that you wrote a removeItem method, you might have been a little disappointed that you had to use an anonymous function like:

1
<button data-bind="click: function() { viewModel.removeItem($data); }">Delete</button>

Update: Knockout 2.0 eliminates this pain point by automatically passing the data as the first argument.

With event delegation, we could possibly add a binding to a parent element and just call a method off of the view model passing the actual data that was returned through tmplItem as part of the binding.

Creating a custom binding for delegated clicks

Here is what I started with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ko.bindingHandlers.delegatedClick = {
    init: function(element, valueAccessor, allBindings, viewModel) {
        var callback = valueAccessor();
        //build a function that will call our method and pass the real data to it
        var realValueAccessor = function() {
            return function(event) {
                var element = event.target;
                    //get real context
                    var context = $(event.target).tmplItem().data;
                    return callback.call(viewModel, context, event);
            }
        }

        //call real click binding's init
        ko.bindingHandlers.click.init(element, realValueAccessor, allBindings, viewModel);
    }
};

So, we are just trying to prepare a function that we can pass on to the real click binding. The function uses tmplItem to retrieve the actual data object related to the element that was clicked and then executes the callback that was passed in through the binding. The actual data object is passed to the function. Now, I can put a binding at the table level to remove an item when I click on a “Delete” button in a cell.

1
<table data-bind="template: {'itemsTmpl', foreach: items }, delegatedClick: removeItem"></table>

This works, but removeItem will get called for Any click inside the table. It doesn’t matter if we click the button, another cell, or a header cell, we will always attempt to run the method. This is certainly not what we want. To limit the elements that trigger our actual method, we can pass in a selector to the binding. As we already have a dependency on jQuery through the jQuery Templates plug-in, we can use jQuery’s .is functionality to make sure that we only proceed when there is a match. Now we pass an object containing our function and a selector to the binding:

1
<table data-bind="template: {'itemsTmpl', foreach: items }, delegatedClick: { callback: removeItem, selector: 'a' }"></table>

In our binding handler, we can make a small modification to check if the element matches our selector.

1
2
3
4
5
if ($(element).is(options.selector)) {
    //get real context
    var context = $(event.target).tmplItem().data;
    return options.callback.call(viewModel, context, event);
}

This works properly when calling parent functions that receive the children as a parameter. However, what if you want to actually call a method off of the child object in this manner? I found that based on the way that Knockout does parsing of the bindings, something like this will not work:

1
<table data-bind="delegatedClick: { callback: childFunction, selector: 'td' }"></table>

This will error out before it gets to my binding, unless childFunction actually exists on the data that is the context of the table’s data binding (the view model passed to applyBindings, unless you are in a template). We can still make this work though by passing the function name as a string and reconciling it in the binding.

1
<table data-bind="delegatedClick: { callback: 'childFunction', selector: 'td' }"></table>

For a minute, I was disappointed that it requires passing a string for the callback, but then I came to the realization that really the whole data-bind attribute is just a string anyways.

Now, I can check for this in the binding:

1
2
3
4
5
6
7
//get real context
var context = $(event.target).tmplItem().data;
if (typeof options.callback === "string" && typeof context[options.callback] === "function") {
    return context[options.callback].call(context, event);
}

return options.callback.call(viewModel, context, event);

If the callback supplied in the binding is a string, then we will call the function off of the child object passing just the event to it. If the function is not a string, then we will call it off of the parent object and pass the child object and the event to it. This allows us to decide whether we want to use a method on our view model or a method on our child object.

One limitation that I immediately ran into was that I might want to pass in multiple click handlers that use different selectors. We should be able to pass in a list of handlers to add as an array.

So, my final version looked like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ko.bindingHandlers.delegatedClick = {
    init: function(element, valueAccessor, allBindings, viewModel) {
        var clicks = valueAccessor();
        ko.utils.arrayForEach(clicks, function(options) {
            var realValueAccessor = function() {
                return function(event) {
                    var element = event.target;
                    if ($(element).is(options.selector)) {
                        //get real context
                        var context = $(event.target).tmplItem().data;
                        if (typeof options.callback === "string" && typeof context[options.callback] === "function") {
                            return context[options.callback].call(context, event);
                        }
                        return options.callback.call(viewModel, context, event);
                    }
                }
            }

            //call real click binding's init
            ko.bindingHandlers.click.init(element, realValueAccessor, allBindings, viewModel);
        });
    }
};

and I can bind to multiple click events like:

1
<table data-bind="template: 'rowsTmpl', delegatedClick: [{callback: 'toggleShowDetails', selector: 'td a' }, { callback: deleteRow, selector: 'button' }]"></table>

Generic binding for all events

Finally, it might be nice to respond to other events in this way, like mouseover and mouseout. Based on the existing event binding, here is a more generic version of the delegatedClick binding:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//binding to do event delegation for any event
ko.bindingHandlers.delegatedEvent = {
    init: function(element, valueAccessor, allBindings, viewModel) {
        var eventsToHandle = valueAccessor() || {};
        //if a single event was passed, then convert it to an array
        if (!$.isArray(eventsToHandle)) {
            eventsToHandle = [eventsToHandle];
        }
        ko.utils.arrayForEach(eventsToHandle, function(eventOptions) {
            var realCallback = function(event) {
                var element = event.target;
                var options = eventOptions;
                //verify that the element matches our selector
                if ($(element).is(options.selector)) {
                    //get real context
                    var context = $(event.target).tmplItem().data;
                    //if a string was passed for the function, then assume it is a function of the real context
                    if (typeof options.callback === "string" && typeof context[options.callback] === "function") {
                        return context[options.callback].call(context, event);
                    }
                    //if a function was passed, then give it the real context as a param
                    return options.callback.call(viewModel, context, event);
                }
            }

            var realValueAccessor = function() {
                var result = {};
                result[eventOptions.event] = realCallback;
                return result;
            }

            ko.bindingHandlers.event.init(element, realValueAccessor, allBindings, viewModel);
        });
    }
};

We could bind to the mouseover and mouseout events of our header cells like:

1
<thead data-bind="delegatedEvent: [{ event: 'mouseover', callback: setDescription, selector: 'th'}, { event: 'mouseout', callback: clearDescription, selector: 'th'}]">

I can also now rewrite the delegatedClick binding to be a wrapper to this generic binding.

Here is a grid-like sample that shows using event delegation when binding to a function on the view model, binding to a function on a cell’s related object, and binding to the mouseover and mouseout events on the header cells:

Link to full sample on jsFiddle.net:

Link to full sample on jsFiddle.net

In Knockout, event delegation is probably not necessary in most cases. However, I think that there are scenarios where this could be useful and beneficial, especially when dealing with an extremely large number of elements that need their events handled in similar ways.