Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Tekpub Refactoring Knockout.js Screencast

| Comments

Recently, I had the chance to do some pair programming with Rob Conery to refactor a Knockout-based shopping cart for a Tekpub full throttle video. Rob has had a love/hate relationship with Knockout over the years and I have had several discussions with him in the past trying to work through some of his concerns.

He recently asked me to take a look at a Knockout-based shopping cart that he had written for another video. The code was working just fine, but as I dug into it, I started jotting down a LOT of notes. I ended up formatting them in markdown and sent them Rob’s way. He thought that it would be a good idea to record a screencast to really dig into those notes.

Here is a trailer for the screencast:

I personally have had a Tekpub subscription for around two years and definitely recommend the service. They are really putting out some great content lately and have a well-done Knockout series.

Update: here is a link to the notes that I sent Rob.

A Simple Editor Pattern for Knockout.js

| Comments

Implementing an editor that allows users to accept or cancel their changes is a common task in Knockout.js. Previously, I suggested the idea of a protectedObservable that is an extended observable with the ability to commit and reset the value being edited. Since Knockout 2.0, I would probably now implement that functionality using either extenders or by augmenting the .fn object of the core types. However, I now use a different pattern.

I described this pattern in the Twin Cities Code Camp presentation here. This technique allows you to easily copy, commit, and revert changes to an entire model. There are two rules that make this pattern work:

  1. The creation of observables and computeds needs to be separate from actually populating them with values.

  2. The format of the data that you put in should be the same as the format that you are able to get out. Typically this means that calling ko.toJS on your model should result in an object that you could send back through the function described in step 1 to re-populate the values.

Separate creation and initialization

The idea is that we can apply fresh data to our object at anytime, so our constructor function should just create our structure, while a separate method handles setting the values.

1
2
3
4
5
6
7
8
9
10
11
    var Item = function(data) {
        this.name = ko.observable();
        this.price = ko.observable();

        this.update(data);
    };

    Item.prototype.update = function(data) {
        this.name(data.name || "new item");
        this.price(data.price || 0);
    };

Our update function (call it whatever you like init, initialize, hydrate, etc.), needs to handle populating the values of all of the observables given a plain JavaScript object. It can also handle supplying default values.

Data in matches data out

To make this pattern work, we need to be able to put a plain JavaScript object in and get the equivalent object out. Ideally, simply calling ko.toJS on our model, will give us the plain object that we need. It is okay, if there are some additional computeds/properties on the object, but it needs to be appropriate for sending through the update function again.

A technique that I use frequently to “hide” data that I won’t want when I turn the model into a plain JavaScript object or to JSON is to use a “sub-observable”. So, if we had a computed observable to show a formatted version of the price that we don’t want in our output, we could specify it like:

1
2
3
4
5
6
7
    var Item = function(data) {
        this.name = ko.observable();
        this.price = ko.observable();
        this.price.formatted = ko.computed(this.getFormattedPrice, this);

        this.update(data);
    };

Since observables are functions and functions are objects that can have their own properties, it is perfectly valid to create a formatted computed off of the price observable. In the markup, we can bind against price.formatted. However, when calling ko.toJS or ko.toJSON, the formatted sub-observable will disappear, as we will simply be left with a price property and value.

Reverting changes

Now that we have these pieces in place, it is easy to create an editor that allows reverting changes. In the event that a user chooses to cancel their editing, all we need to do is send the original data back through our update function and we will be back to where we started.

We will need to track the original data, so that it is available to repopulate the model. A good place to cache this data is in the update function itself. It is useful to make sure that the data is “hidden” as well, so multiple edits don’t result in recursive versions of the old data being kept around. If we were not using the prototype, then we could just use a local variable to store the data, but since we are placing our shared functions on the prototype in these samples, we need to find another way to hide this data. A simple solution is to create an empty cache function and store our data as property off of it. This will prevent calls to ko.toJS or ko.toJSON from capturing this data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    var Item = function(data) {
        this.name = ko.observable();
        this.price = ko.observable();
        this.cache = function() {};

        this.update(data);
    };

    ko.utils.extend(Item.prototype, {
      update: function(data) {
        this.name(data.name || "new item");
        this.price(data.price || 0);

        //save off the latest data for later use
        this.cache.latestData = data;
      },
      revert: function() {
        this.update(this.cache.latestData);
      }
    });

In this scenario, we will let edits persist to our observables and in the event that a user chooses to cancel, we will refresh our model with the original data.

Committing changes

When a user accepts the data, we need to make sure that we update the cached data with the current state of the model. For example, we could simply add a commit or accept function to our prototype that does:

1
2
3
    commit: function() {
        this.cache.latestData = ko.toJS(this);
    }

We now have the latest data cached, so the next time that a user cancels it will be reverted back to this updated state.

Copying/cloning

Sometimes in an editor, we would not want the currently edited observables to affect the rest of the UI until the user chooses to accept the changes. In this case, we can use these same methods to create a copy of the item for editing and then apply it back to the original. Here is how our overall view model might look:

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
36
37
38
    var ViewModel = function(items) {
        //turn the raw items into Item objects
        this.items = ko.observableArray(ko.utils.arrayMap(items, function(data) {
            return new Item(data);
        });

        //hold the real currently selected item
        this.selectedItem = ko.observable();

        //make edits to a copy
        this.itemForEditing = ko.observable();
    };

    ko.utils.extend(ViewModel.prototype, {
        //select and item and make a copy of it for editing
        selectItem: function(item) {
            this.selectedItem(item);
            this.itemForEditing(new Item(ko.toJS(item)));
        },

        acceptItem: function(item) {
            var selected = this.selectedItem(),
                edited = ko.toJS(this.itemForEditing()); //clean copy of edited

            //apply updates from the edited item to the selected item
            selected.update(edited);

            //clear selected item
            this.selectedItem(null);
            this.itemForEditing(null);
        },

        //just throw away the edited item and clear the selected observables
        revertItem: function() {
            this.selectedItem(null);
            this.itemForEditing(null);
        }
    });

So, we make edits to a copy of the original and on an accept we apply those edits back to the original. We can use ko.toJS to get a plain JavaScript object and feed it into our update function to apply the changes. In this scenario, the revertItem function can simply throw away the copied item and clear our the selected value.

Link to sample on jsFiddle.net

If you were going to reuse this technique often, then you could even create an extension to an observableArray that adds the appropriate observables and functions off of the observableArray itself like in this sample.

Updating from server

Our update function is also a handy way to apply updates from the server to our existing view model. In our case, we would likely need to add an id to the Item objects, so that we can identify which object needs updating. This works very well with something like SignalR or socket.io to keep various clients in sync with each other. This is an easier pattern than trying to swap items in an array or update specific properties one by one.

Summary

Editors with accept/cancel options are a common scenario in a Knockout.js application. By separating the creation of observables/computeds from poplating their values, we can commit, reset, and update models by feeding a version of the data through our update function. When you think in terms of the plain JS object that you can get out of or put into a model, it makes many of these scenarios easy to handle.

Revisting Event Delegation in Knockout.js

| Comments

Previously, I discussed a technique to do event delegation in Knockout.js. That post was created before Knockout 2.0, which added the ability to use ko.dataFor and ko.contextFor to retrieve the data context related to a specific element (as described here). With these new tools, there are now much better ways to add a single handler on a parent element that can respond to events triggered on its child elements.

Note: based on the techniques in this post, I created a plugin called knockout-delegatedEvents located here: https://github.com/rniemeyer/knockout-delegatedEvents.

Why event delegation in Knockout.js?

Currently when you use the click or event binding in Knockout, an event handler is attached directly to that element. Generally this is fine and causes no real problems. However, if you face a scenario where you have a large number of elements that require these bindings, then there can be a performance impact to creating and attaching these handlers to each element, especially in older browsers. For example, you may be creating a grid where each cell needs various event handlers or a hierarchical editor where you are potentially attaching handlers to interact with the parent items as well as each child (and each child may have children).

In this case, there are advantages to using event delegation:

  • you only have to attach one (or a few) event handlers rather than one on each element. Events bubble up to the higher-level handler and can understand the original element that triggered it.
  • dynamically added content does not need new event handlers added to them (largely not an issue with KO bindings and templating)

Normal event delegation with KO 2.0+

With Knockout 2.0+, the common way to use event delegation is to avoid attaching event handlers in the bindings themselves and add another layer of code to attach a single handler to a parent element using something like jQuery’s on (previously live/delegate). So, rather than doing:

1
2
3
4
5
<ul data-bind="foreach: items">
    <li>
        <a href="#" data-bind="click: $root.selectItem, text: name"></a>
    </li>
</ul>

You would instead do:

1
2
3
4
5
<ul id="items" data-bind="foreach: items">
    <li>
        <a href="#" data-bind="text: name"></a>
    </li>
</ul>

and attach an event handler elsewhere like:

1
2
3
4
5
6
$("#items").on("click", "a", function() {
    var context = ko.contextFor(this); //this is the element that was clicked
    if (context) {
        context.$root.selectItem(context.$data);
    }
});

Attaching a single handler like this works quite well. However, there are a few issues that I still have with this technique:

  • you have another layer of code to manage besides your view model. This is the type of code that Knockout generally helps you get away from.
  • this code is tightly coupled (fairly) with this view’s markup
  • in our example, if the entire ul is part of a template that gets swapped, then we would need to hook up our handler again or choose to add our handler at a higher level (possibly up to the body)
  • this particular code also adds a dependency on jQuery that is not really necessary

In my perfect Knockout application, you have your view and your view model with the only code outside of that being a call to ko.applyBindings. To accomplish this, we would need to be able to wire it up declaratively in the view. I have been exploring a few ways to make this happen.

Declaratively adding a handler

The first step is attaching a handler on the parent or root element that will respond when events bubble up to it. This is pretty easy to accomplish with a custom binding. When the event is handled, we can determine the original element and use ko.dataFor or ko.contextFor to get the original data context and then execute some method.

1
2
3
4
5
6
7
8
9
10
//add a handler on a parent element that reponds to events from the children
ko.bindingHandlers.delegatedHandler = {
    init: function(element, valueAccessor) {
        //array of events
        var events = ko.utils.unwrapObservable(valueAccessor()) || [];
        ko.utils.arrayForEach(events, function(event) {
            ko.utils.registerEventHandler(element, event, createDelegatedHandler(event, element));
        });
    }
};

To create the handler, we take the target element and get its context:

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
//create a handler for a specific event like "click"
var createDelegatedHandler = function(eventName, root) {
    //return a handler
    return function(event) {
        var el = event.target,
            data = ko.dataFor(el),
            action = findAnAction(el, context); //??? how do we know what method to call


        //execute the action
        if (action) {
            //call it like KO normally would with the data being both the value of `this` and the first arg
            result = action.call(data, data, event);

            //prevent the default action, unless the function returns true
            if (result !== true) {
                if (event.preventDefault) {
                    event.preventDefault();
                }
                else {
                    event.returnValue = false;
                }
            }
        }
    };
};

When used on an element, the binding would take in an array of events like:

1
2
3
<ul data-bind="delegatedHandler: ['click', 'mouseover', 'mousedown']">
...
</ul>

With this binding in place, we can handle the event, find the element that triggered the event, and determine the data context of the element. However, we still need to know what method to call. I looked at a few alternatives for this piece.

Option #1 - using a binding

On the child elements, we could use a binding that stores a reference to the function to call. The binding can use Knockout’s ko.utils.domData functions to store and retrieve data on the element, as Knockout automatically cleans up this data whenever it removes elements from the document. For example, we could create a delegatedClick binding to make this association.

1
2
3
4
5
6
7
//associate a handler with an element that will be executed by the delegatedHandler
ko.bindingHandlers.delegatedClick = {
    init: function(element, valueAccessor) {
        var action = valueAccessor();
        ko.utils.domData.set(element, "ko_delegated_click", action);
    }
};

Now, on a child element, you would associate a function like:

1
2
3
<li>
    <a href="#" data-bind="text: name, delegatedClick: $parent.selectItem"></a>
</li>

We could create additional bindings for other events that we are interested in and could even create a helper function to generate these bindings given an array of events that we need to handle.

Pros

  • Can simply replace click bindings with delegatedClick (and other events) without any other changes
  • Can bind against specific functions and can execute code for cases where you need to call a function with a parameter to create a handler (delegatedClick: $parent.createHandler('some_modifier'))

Cons

  • The binding on the child elements is lightweight, but we still have to pay the overhead of parsing and executing a binding.

Option #2 - use a data- attribute with method name

The first solution works well, but I still do not like having to pay the price of executing bindings on the child elements. For an alternative solution, I looked at a different approach where the child elements are tagged with a data-eventName attribute that contains the method name.

1
2
3
<li>
    <a href="#" data-click="selectItem" data-bind="text: name"></a>
</li>

With this technique we would no longer need the child bindings. In the event handling code, we would now need to locate a method that matches our data-eventName attribute value. This time we can use ko.contextFor to get back the entire binding context. It is very common to call functions off of $parent or $root as well as off of the current data itself, so the $parents array allows us to walk up the scope chain to locate an appropriate method.

Pros

  • No overhead of executing a binding on the child elements
  • Can execute the function with the correct owner, so we do not need to use .bind or var self = this; to ensure that the context is correct. This is a really nice benefit, we have to search for the method in each scope, so we know the appropriate owner of the method to use as the context.

Cons

  • could run into issues with clashing names (child has an add method, but you wanted to call parent’s add)
  • cannot execute code to create a handler, as the attribute value would be a string that needs to match a method name

Option #3 - associate method and name in root/global object

Another alternative might be to use the data- attribute, but use the string to key into an object that holds the associations between names and actions. Perhaps something like ko.actions or ko.commands. This object could hold just function references or maybe a function and owner. For arrays of objects, we would not be able to indicate an appropriate owner, so we would have to fall back to making sure that the function is properly bound on each instance. For example, we could even give the function an alias of select like:

1
2
3
4
ko.actions.select = {
    action: this.selectItem,
    owner: this
};

Pros

  • No overhead of executing a binding on the child elements
  • No issues with ambiguity in names, as names can be aliased
  • Functions outside of a view model could be included
  • Could dynamically change the definition of the action

Cons

  • have to manage adding (and maybe removing) functions to this object
  • different types of objects with the same method names would have to be aliased with unique names in this object

Summary

In cases where you need to attach a large number of event handlers, using event delegation can be a nice win. Attaching these handlers declaratively provides an alternative to wiring this up outside of your view/viewmodel. While considering each of the techniques to determine the function to execute, I was leaning towards #2, as it does not require the child bindings and as a bonus gets the context right when it calls the handler. Instead though, I decided to create a plugin that can use all of these techniques interchangeably. The plugin lives at: http://github.com/rniemeyer/knockout-delegatedEvents. Please let me know if you have feedback or suggestions for the plugin.

Knockout.js 2.2 Is Out Now!

| Comments

Just a quick note that Knockout 2.2 is now available. You can read more about the details of this release in the announcement or Steve Sanderson’s blog post.

Here are a few of my favorite changes:

  • The css binding can either toggle specific classes on and off like (css: { error: hasError, required: isRequired }) or now add one or more classes dynamically like css: someClassOrClasses. This is handy when the class names are dynamic. Previously, you could do something similar with the attr binding against the class attribute, but it would overwrite the class attribute completely. When using the css binding in this way, it will now preserve existing classes and will properly remove any class or classes that were previously added by the binding when the value changes.
  • The if and ifnot bindings now only re-render their contents when the bound value changes between truthy and falsy. This helps to alleviate the performance issue described in the first half of this post.
  • There is a new peek function available on observables and computed observables. This allows you to retrieve its value without creating a dependency. This definitely can be useful when you want tight control over when bindings and other subscriptions are triggered, but I would use caution as it does circumvent the normal dependency tracking mechanism. If you find that you need this functionality, then you probably have a good reason why you don’t want a dependency and would understand what does and doesn’t trigger updates.
  • The foreach binding and the foreach option of the template binding now try to understand if items have been moved and will relocate the content rather than tearing it down and re-rendering it at its new position. This comes with some new callbacks (beforeMove and afterMove).
  • The ko variable is now available from within bindings, even if ko is not a global variable (common in AMD scenarios).

You can download the 2.2 files from here. There were no intentional breaking changes made in 2.2 (unless someone was relying on an existing bug), so if you have any issues with your application not behaving as it did in 2.1, please log an issue here.

Getting the Most Out of Knockout.js Session From Twin Cities Code Camp

| Comments

Last weekend, I was invited to talk about Knockout.js at Twin Cities Code Camp. It was a very enjoyable day, as I saw several great presentations and had a chance to talk to many Knockout users.

I was told that there had been several sessions in the past that touched on Knockout, so for this talk I decided to focus on some tips, tricks, and techniques that can be used to get the most out of Knockout. The topics covered included:

  • Brief introduction to Knockout
  • Getting data in and out
  • Extending Knockout’s structures
  • Custom Bindings
  • Performance tips

There was a small amount of overlap with the session that I did at ThatConference in August, but all of the samples were new for this talk. The project that I used to give the presentation is also described in the ThatConference post and a reference project lives here.

Here is a screencast of the presentation:

Getting the Most Out of Knockout.js - 10/06/2012 - Twin Cities Code Camp

A few clarifications from the presentation:

  • At the end of the “getting data out” part, I had typed var meta instead of this.meta.
  • In the maxLength extender, I used a writeable computed observable. In the write function, I should have written the new value to our observable (original(newValue);), in addition to checking its length.
  • In the enterKey custom binding, you probably would want to return the result of the actual function call, as when you return true from a handler called by the event binding, it will allow the default action to proceed, which can be useful at times.
  • In the modal binding where we add a handler for the hidden event, you may want to check if what you are dealing with can be written to as an observable (is an observable or writeable computed observable) by calling ko.isWriteableObservable. So, we might want to call ko.isWriteableObservable(data) before doing data(null).
  • In the modal binding, the wrappers to the with binding, should really be written with with quoted like return ko.bindingHandlers['with'].init.apply(this, arguments); and return ko.bindingHandlers['with'].update.apply(this, arguments);. In IE < 9 the use of with as a property name will cause an error, as it is a JavaScript keyword.

Participating in the code camp was definitely worth the effort of traveling up to the Twin Cities. I would be happy to hear any type of feedback on the presentation. Photo by @JudahGabriel.