Knock Me Out

spacer

Thoughts, ideas, and discussion about Knockout.js

Using KO’s Native PubSub for Decoupled Synchronization

| Comments

In the previous post, I suggested some ideas for binding against multiple view models in Knockout.js. When working with more than one view model, a common task is deciding how to communicate between the separate components. Creating direct references between them often doesn’t feel right and can lead you down a path where each view model has lost its independence and cannot be used effectively without the other objects.

To handle this situation, we can keep our components loosely coupled by using a messaging system where each view model or component does not need to have direct references to its counterparts. There are several benefits to using this technique:

  • Components remain independent. They can be developed, tested, and used in isolation.
  • Knockout view models and non-KO components can communicate using a common interface without direct knowledge of each other.
  • Components can be safely refactored. Properties can be renamed, moved, and adjusted without worrying about breaking compatibility.

Knockout’s native pub/sub

There are already many libraries and options for providing this type of message bus. One option might be triggering custom events or using great pub/sub libraries like amplify.js or postal.js. While these libraries provide robust capabilities, Knockout itself already has all of the tools to do basic pub/sub communication built-in. This is part of the ko.subscribable type, which has its capabilities added to observables, computed observables, and observableArrays.

Normally, you would not construct a ko.subscribable directly, but it is easy enough to do so:

1
var postbox = new ko.subscribable();

In Knockout 2.0, support was added for topic-based subscriptions to aid in sending out beforeChange notifications. To create a topic-based subscription against our ko.subscribable, we would simply do:

1
2
3
4
5
6
postbox.subscribe(callback, target, topic);

//with some actual code
postbox.subscribe(function(newValue) {
    this.latestTopic(newValue);
}, vm, "mytopic");

To send out a notification on this topic we would do:

1
postbox.notifySubscribers(value, "mytopic");

Now, we can do basic pub/sub messaging using subscribe and notifySubscribers against our mediator, the postbox object. However, whenever I explore integrating a new technique with Knockout, I try to consider how to make it feel as easy and natural as possible.

Extending Observables

A typical scenario for this type of functionality would be that we want to synchronize an observable between view models. This might be a one-way or even a two-way conversation. To make this easy, we can look at extending the ko.subscribable type, which would affect observables, observableArrays, and computed observables.

Suppose that we want to setup an observable to automatically publish on a topic whenever it changes. We would want to set up a manual subscription against that observable and then use notifySubscribers on our postbox.

1
2
3
4
5
6
7
8
9
10
ko.subscribable.fn.publishOn = function(topic) {
    this.subscribe(function(newValue) {
        postbox.notifySubscribers(newValue, topic);
    });

    return this; //support chaining
};

//usage of publishOn
this.myObservable = ko.observable("myValue").publishOn("myTopic");

Now, whenever the observable’s value changes, it will publish a message on the topic.

On the other side, we might want to make it easy for an observable to update itself from messages on a topic. We can use this same concept:

1
2
3
4
5
6
7
8
ko.subscribable.fn.subscribeTo = function(topic) {
    postbox.subscribe(this, null, topic);

    return this;  //support chaining
};

//usage
this.observableFromAnotherVM = ko.observable().subscribeTo("myTopic");

Notice that we can just pass this (the observable) in as the callback to the subscribe function. We know that an observable is a function and that it will have its value set when you pass an argument into the function. So, there is no need to write something like:

1
2
3
4
//no need to add an extra anonymous function like:
postbox.subscribe(function(newValue) {
    this(newValue);
}, this, topic);

Now, our observables can exchange information without direct references to each other. They do need to agree on a topic, but do not have any knowledge about the internals of the other view model or component. We can even mock or simulate the other components in testing scenarios by firing messages.

Potential gotcha: publishing objects

If the new value being published is an object, then we need to be careful, as both sides will have a reference to the same object. If code from multiple view models makes changes to that object, then we are likely no longer as decoupled as we would like. If the object is simply configuration/options passed as an object literal that are not modified, then this seems okay. Otherwise, it is preferable to stick to primitives in the values being published. Another alternative is to use something like ko.toJS to create a deep-copy of the object that is being published, so neither side has the same reference.

A new library: knockout-postbox

I created a small library called knockout-postbox to handle this type of communication. It uses the techniques described above and adds a bit of additional functionality:

  • creates a ko.postbox object with clean subscribe and publish methods that take a topic as the first argument.
  • adds a subscribeTo function to all subscribables (observables, observableArrays, and computed observables). The subscribeTo function also allows you to initialize your observable from the latest published value and allows you to pass a transform function that runs on the incoming values.
  • adds an unsubcribeFrom function to all subscribables that removes subscriptions created by subscribeTo.
  • adds a publishOn function to all subscribables that automatically publishes out new values. The publishOn function also allows you to control whether you want to immediately publish the initial value, and lets you supply an override function (equalityComparer) that allows you to compare the old and new values to determine if the new value should really be published.
  • adds a stopPublishingOn function to all subscribables that removes the subscription that handles the publishOn messages.
  • adds a syncWith function that does both a subscribeTo and a publishOn on the same topic for two-way synchronization.

Project link: https://github.com/rniemeyer/knockout-postbox

Basic sample:

Link to sample on jsFiddle.net

Next post: using this concept to integrate with client-side routing in a way that is natural to Knockout

Quick Tip: Telling Knockout to Skip Binding Part of a Page

| Comments

Recently, I worked with several people on questions related to binding multiple view models in a single page. One common approach is to bind a view model to a particular root element using a call like ko.applyBindings(vm, containerNode);. However, a limitation with this approach is that when binding multiple view models, none of the container elements can overlap. This means that you could not bind one view model nested inside of another.

One way to address this issue is to create a top level view model that contains your “sub” view models and then call ko.applyBindings on the entire page with the overall view model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var profileModel = {
    first: ko.observable("Bob"),
    last: ko.observable("Smith")
};

var shellModel = {
    header: ko.observable("Administration"),
    sections: ["profile", "settings", "notifications"],
    selectedSection: ko.observable()
};

//the overall view model
var viewModel = {
    shell: shellModel,
    profile: profileModel
};

ko.applyBindings(viewModel);

Now in the view, you can use the with binding along $root to bind a nested view model:

1
2
3
4
5
6
7
<div data-bind="with: shell">
    <h2 data-bind="text: header"></h2>
    <div data-bind="with: $root.profile">
        ...
    </div>
    ...
</div>

This technique is nice, because you only have to make a single ko.applyBindings call and you can use $root or $parent/$parents to access data at any time from another view model. However based on a desire to keep our code modular and to control how and when elements are bound, it is often not convenient or practical to build a top level view model.

With Knockout 2.0, there is a simple alternative that can provide for greater flexibility. Bindings are now able to return a flag called controlsDescendantBindings in their init function to indicate that the current binding loop should not try to bind this element’s children. This flag is used by the template and control-flow bindings (wrappers to the template binding), as they will handle binding their own children with an appropriate data context.

For our scenario, we can take advantage of this flag and simply tell Knockout to leave a certain section alone by using a simple custom binding:

1
2
3
4
5
ko.bindingHandlers.stopBinding = {
    init: function() {
        return { controlsDescendantBindings: true };
    }
};

Now, we can bind our “shell” model to the entire page and bind our “profile” model to the specific container:

1
2
3
4
5
6
7
8
9
10
11
12
13
var profileModel = {
    first: ko.observable("Bob"),
    last: ko.observable("Smith")
};

var shellModel = {
    header: ko.observable("Administration"),
    sections: ["profile", "settings", "notifications"],
    selectedSection: ko.observable()
};

ko.applyBindings(shellModel);
ko.applyBindings(profileModel, document.getElementById("profile"));

In our view, we can now use the simple stopBinding custom binding around our inner container element:

1
2
3
4
5
6
7
8
9
10
<div>
    <h2 data-bind="text: header"></h2>
    <div data-bind="stopBinding: true">
        <div id="profile">
            <input data-bind="value: first" />
            <input data-bind="value: last" />
        </div>
    </div>
    ...
</div>

Adding the extra div to hold our stopBinding binding may not cause our app any problems, but if it does then in KO 2.1 we can now create containerless custom bindings by adding our binding to ko.virtualElements.allowedBindings.

1
2
3
4
5
6
7
ko.bindingHandlers.stopBinding = {
    init: function() {
        return { controlsDescendantBindings: true };
    }
};

ko.virtualElements.allowedBindings.stopBinding = true;

and finally we can clean up our view to look like:

1
2
3
4
5
6
7
8
9
<div>
    <h2 data-bind="text: header"></h2>
    <!-- ko stopBinding: true -->
    <div id="profile">
        <input data-bind="value: first" />
        <input data-bind="value: last" />
    </div>
    <!-- /ko -->
</div>

With this simple binding, we can now compose pages with multiple view models without the worry of conflicting/overlapping bindings.

Here is a live sample:

Link to full sample on jsFiddle.net

Knockout 2.1 Is Out

| Comments

Knockout.JS 2.1 is now available! Minified and debug versions available here. This release focused mainly on performance improvements and bug fixes after the 2.0 release.

Here are some highlights:

  1. CommonJS and AMD support
  2. Great performance improvements to templating - use cloned DOM nodes when possible rather than parsing strings into elements.
  3. Support for custom container-less bindings
  4. Ability to extend the binding context in a custom binding
  5. Computed observables no longer can recursively evaluate themselves (I think that has happened to all of us a few times!)
  6. $index is available in foreach scenarios from the binding context. There is no longer a need for workarounds like storing an index as a property of your data items. Note that $index is an observable, so it needs to be reference using $index() in expressions.
  7. $parentContext is available from the binding context as well. While, $parent contains the actual data from one scope level up, $parentContext contains the actual binding context ($data, $parent, $parents, $root, etc.) of the parent.
  8. ko.isComputed is a helper function that was added to determine if a value is a computed observable or not.
  9. ko.toJSON now passes its arguments on to JSON.stringify after calling ko.toJS. This makes creating a handy “debug” section even easier <pre data-bind="text: ko.toJSON($root, null, 2)"></pre> and this can be used to include only certain properties in the converted JSON like ko.toJSON(person, ['firstName', 'lastName']). This seems even more flexible in many cases than the approach that I mentioned here.
  10. Better support for using a single instance of KO between documents - you can now pass an instance of KO between documents and have bindings work properly across the documents. Previously the computed observables associated with bindings would dispose themselves when outside of the original document as they believed they were no longer part of the DOM.
  11. Numerous other bug and performance fixes

For this release Michael Best joined the core team and drove the majority of the changes with Steve. Michael has numerous other exciting performance enhancements in his fork that will be evaluated for possible inclusion (in a non-breaking fashion) in the core. He has really helped tremendously in moving the project forward.

Please report any issues found here.

Giving the Blog a Facelift

| Comments

I just finished the process of moving this blog from Blogger to an Octopress site that uses Jekyll to generate a static blog. I am excited to have better control over the look and feel of the blog and to move to a git-based workflow using markdown files for posts.

Here are a few of the steps that I took to make this move:

  • Converted Blogger site to use disqus for comments
  • Imported old comments using the import tool from disqus
  • Imported blog posts into Octopress using the script here to at least get some basic structure and permalinks.
  • At that point, I scrapped all of the HTML for each post and recreated each in markdown. It was a painful and time consuming process, but the posts will hopefully now look consistent and will be easier to edit/update in the future.
  • Made updates/change to the layout and look of the default template.
  • Created an app in Heroku, pushed the site to it, and added the custom domain.
  • Pointed the domain to Heroku.
  • Updated the Feedburner feed to use the new RSS.

I was never too excited about the look, feel, and process of updating the old site. I was mostly focused on getting content out there and a Blogger blog was an easy way to get started. I am excited to continue tweaking the site and using git and markdown for the posts. Hopefully, I will find it easier to make more frequent posts and to update older posts as Knockout changes.

Knockout.js Performance Gotcha #2 - Manipulating observableArrays

| Comments

Most Knockout.js applications tend to deal with collections of objects that are stored in one or more observableArrays. When dealing with observableArrays, it is helpful to understand exactly how they work to make sure that you donít hit a performance issue.

An observableArray is simply an observable with some additional methods added to deal with common array manipulation tasks. These functions get a reference to the underlying array, perform an action on it, then notify subscribers that the array has changed (in KO 2.0, there is now an additional notification prior to the change as well).

For example, doing a push on an observableArray essentially does this:

1
2
3
4
5
6
7
ko.observableArray.fn.push = function () {
    var underlyingArray = this();
    this.valueWillMutate();
    var result = underlyingArray.push.apply(underlyingArray, arguments);
    this.valueHasMutated();
    return result;
};

Pushing items to an observableArray

Let’s consider a common scenario where we have a collection of objects along with a computed observable to track a filtered array of those objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
var ViewModel = function() {
    this.items = ko.observableArray([
        new Item("Task One", "high"),
        new Item("Task Two", "normal"),
        new Item("Task Three", "high")
    ]);

    this.highPriorityItems = ko.computed(function() {
        return ko.utils.arrayFilter(this.items(), function(item) {
            return item.priority() === "high";
        });
    };
};

Now, say we want to add additional data from the server to the observableArray of items. It is easy enough to loop through the new data and push each mapped item to our observableArray.

1
2
3
4
5
6
7
this.addNewDataBad = function(data) {
    var item;
    for (var i = 0, j = newData.length; i < j; i++) {
        item = newData[i];
        self.items.push(new Item(item.name, item.priority



gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.