Site Meter
 
 

Knockout Projections – a plugin for efficient observable array transformations

knockout-projections is a new Knockout.js plugin that adds efficient “map” and “filter” features to observable arrays. This means you can:

  • Write myObservableArray.map(someMappingFunction) to get a new, read-only array-valued observable containing the mapped version of each input item.
  • Write myObservableArray.filter(someFilterFunction) to get a new, read-only array-valued observable containing a subset of the input items.

When the underlying myObservableArray changes, or whenever any observable accessed during the mapping/filtering changes, the output array will be recomputed efficiently, meaning that the map/filter callbacks will only be invoked for the affected items.

The point of all this: it can scale up to maintain live transforms of large arrays, with only a fixed cost (not O(N)) to propagate typical changes through the graph of dependencies.

Trivial mapping example

To illustrate the mechanics in an obvious way, consider this underlying array:

var numbers = ko.observableArray([1, 2, 3]);

Now if you’ve referenced knockout-projections, you can write:

var squares = numbers.map(function(x) { return x*x; });

Initially, squares will contain [1, 4, 9]. It’s observable, so you can use squares.subscribe to get notifications when it mutates, or you can bind it to a DOM element (e.g., foreach: squares) in KO’s usual way.

Now if you transform the underlying array:

numbers.push(8);

… then squares updates (to [1, 4, 9, 64]) and only calls your mapping function for the new item, 8.

Any transformation is permitted, e.g.:

numbers.reverse();

This has the effect of reversing squares (to [64, 9, 4, 1]), again without remapping anything.

If you remove item(s):

numbers.splice(1, 1);

… then squares is updated (here, to [64, 1]) without remapping anything.

In summary, anyObservableArray.map is an efficient, observable equivalent to Array.prototype.map. Also, it can be arbitrarily chained (and combined in chains with filter) to produce graphs of transformations.

Trivial filtering example

The filter feature works exactly as you’d expect, given the above. For example,

var evenSquares = squares.filter(function(x) { return x % 2 === 0; });

Initially, evenSquares will contain just [64]. When you mutate the underlying array,

numbers.push(4); // evenSquares now contains [64, 16]
numbers.push(5); // evenSquares doesn't change
numbers.push(6); // evenSquares now contains [64, 16, 36]

Again, it responds to arbitrary transformations:

numbers.sort();  // evenSquares now contains [16, 36, 64]

A more realistic use case

Typically you won’t just be playing around will small collections of numbers. Most Knockout apps work with collections of model objects – sometimes very large collections.

Here’s a simple model object:

function Product(data) {
    this.id = data.id;
    this.name = ko.observable(data.name);
    this.price = ko.observable(data.price);
    this.isSelected = ko.observable(false);
}

Many KO applications involve fetching a large collection of model objects and exposing that from a viewmodel:

function PageViewModel() {
    // Some data, perhaps loaded via an Ajax call
    this.products = ko.observableArray([
        new Product({ id: 1, name: 'Klein Burger', price: 3.99 }),
        new Product({ id: 2, name: 'Mobius Fries', price: 1.75 }),
        new Product({ id: 3, name: 'Uncountable Chicken Chunks', price: 3.59 }),
        new Product({ id: 4, name: 'Mandelbrot Salad', price: 2.40 }),
        ... etc ...
    ]);
}
 
ko.applyBindings(new PageViewModel());

Now this might be bound to the UI:

<ul data-bind="foreach: products">
    <li>
        <input type="checkbox" data-bind="checked: isSelected" />
        <strong data-bind="text: name"></strong>
        (Price: £<span data-bind="text: price().toFixed(2)"></span>)
    </li>
</ul>

This is all fine, and Knockout is already good at efficiently (i.e., incrementally) updating the UI when the products array changes. But what if you want to track which subset of products is “selected”?

The traditional approach would be something like:

this.selectedProducts = ko.computed(function() {
    return this.products().filter(function(product) {
        return product.isSelected();
    });
}, this);

This works (using Array.prototype.filter, not the Knockout-projections filter function). However, it’s inefficient. Every time the products array changes, and every time any of their isSelected properties changes, it re-evaluates the isSelected property of every product. It has to do so, because it has no built-in understanding of what you’re doing, so it can’t be clever and incremetally update the earlier selectedProducts array.

However, if you use Knockout-projections filter function, e.g.:

this.selectedProducts = this.products.filter(function(product) {
    return product.isSelected();
});

… it’s both syntactically cleaner, and way faster for large arrays: it now updates the selectedProducts array incrementally whenever either products changes or any of the isSelected values changes.

Similarly you might want to output the names of the selected items. So, you could chain on a new selectedNames property, and perhaps furthermore chain on selectionSummary:

this.selectedNames = this.selectedProducts.map(function(product) {
    return product.name();
});
 
this.selectionSummary = ko.computed(function() {
    return this.selectedNames().join(', ')  || 'Nothing';
}, this);

Now when products changes, or when an isSelected or name property changes, the effect will propagate out incrementally through the whole dependency graph with the minimum of callback-invoking, meaning a snappy user experience even with large data sets and low-end mobile devices.

Licence and origin

knockout-projections is open source under the Apache 2.0 license. It was built as a tiny component in a very large project I’m working on at Microsoft, and thanks to my boss (and his boss, and probably multiple people up the chain) we’ve decided to open-source it. Hopefully my team will be producing plentiful nuggets of OSS goodness for you as we go about our work :)

Just a clarification for the avoidance of any confusion, Knockout.js itself remains MIT licensed and is run by the KO community and the KO core team members (which includes me personally).

A standing desk with (mostly) Ikea parts

Most developers by now are familiar with the standing desk trend. This post isn’t about the pros/cons, suggestions about how long you should stand for, or anything like that. Let’s just say that like me you want to give it a go, but you’re not inclined to spend many hundreds of pounds on a brand new high-end desk until you’re sure you really will use it for the next five years or so.

Many others have built their own standing desks before, even with Ikea parts. So this is hardly revolutionary. But it’s a good looking, inexpensive design, that others may find interesting.

Finished result

This standing desk sits on top of your existing sitting desk, and is designed to take up roughly half of a typical desk width. So you’ll have a half-sitting, half-standing desk, and can easily move between the two postures:

Desk from front

Pros:

  • Transportable. Can easily move it around between desks/workplaces (unscrew the legs by hand, carry it over)
  • Good ergonomics. Gives a lot of room around your keyboard and mouse, and keeps the monitor a pleasing distance from your face, compared with alternatives.
  • Cheap(ish), versus commercial pre-built standing desks, yet looks professional.

Cons:

  • Not height-adjustable, unless you count taking a hacksaw to the legs.
  • Only wide enough for a single large monitor
    • To be fair, there’s nothing to stop you putting on a wider shelf. It’s easily strong enough to take a second large monitor. It would just look a bit more imposing.
    • However, since I switched back to having a single large monitor a few years ago, this is exactly how I want it. Less neck-swivelling = more comfort for me personally.
  • Cheaper DIY alternatives exist
  • Requires a trip to Ikea. But hey, meatballs, right?

Ingredients

Many of the following components can be substituted with alternatives, depending on what you have available. For example, there’s probably no real reason to use Sugru if you already have some strong glue such as Araldite. And any similar-sized piece of wood will do in place of the Ekby Laiva shelf.

From Ikea:

Order online:

Total cost: from £41.24 to £70.99 depending on optional parts. But I do recommend some kind of mat. And I assume you have tools (a drill, a hacksaw) and a range of small screws, since the ones that Ikea include are too big.

Method

When you see the list of parts, it’s pretty obvious how to construct it. So I’m not going to list every screw, cut, or measurement – I’ll just give an outline with some tips. Here’s another viewpoint on the desk to clarify its arrangement:

Desk at angle

Getting the right height

Start here because it’s the most important bit. I’ve read that your standing desk surface should be at roughly elbow-height, but I like mine about 8-10cm below that. You will need to account for the height of the underlying desk and thickness of the table top (obviously), and perhaps the height of your keyboard or anti-fatigue mat if either are big. You might want to first construct a mock-up by balancing the table top on some boxes or whatever.

When you’re sure of the height, measure the Adils legs from the circular bracket at the top downwards, and subtract from your measurement about 1cm to account for the feet you’ll attach later. Now hacksaw the legs to length, and retain the adjustable feet for later.

Screw the circular Adils leg brackets into the underside of the table top now, but don’t screw the legs into them just yet.

Fitting the monitor stand

Be careful when planning how far back to mount the monitor stand to ensure the monitor’s centre of gravity will end up within the desk’s four legs. For me, the desk’s size and the angle of the Capita brackets result in a pretty ideal natural distance from my face to the monitor, but individual preferences vary.

The Capita brackets are a bit baffling because there are so many possible ways of attaching them. I’d recommend first drilling the two 9mm(ish) holes in your table top, loosely bolting on the Capita brackets, and then marking out where the screw holes naturally land on the underside of the Ekby Laiva shelf.

Then drill pilot holes into the Ekby Laiva shelf, bolt the Capita brackets tightly to the table top, and finally screw the Ekby Laiva shelf to the top of the Capita brackets with some small screws.

Finishing the legs

You can now screw in the Adils legs to their brackets, and ta-da – your standing desk is usable. However the bottom end of the legs is just roughly sawn metal, which will make a mess of whatever desk it stands on.

Fortunately you can take the feet off the sawn-off parts of the Adils legs and attach them to the bottom of your new desk’s legs. You can stick them on with strong glue such as Araldite. Instead, I used rolled-out circles of Sugru (see photo below), partly just because I was in a “hack your stuff” mood, and partly because it let me compensate for some irregularities in my unskilled hacksawing:

Desk feet with Sugru

Review

I’ve only been using this for a few weeks now, but the desk feels great to use – it’s sturdy, spacious, and looks good in my office. My elbow pain (the reason I did this in the first place) was gone after the first two days. I tend to stand through the morning and sit in the afternoon, though maybe I should reverse the order to minimise post-lunch lag.

After the first couple of multi-hour standing-working sessions, it was clear I couldn’t just stand on carpet in socks (or shoes) for hours. Your feet will probably tell you the same. I stood on a foam pillow for the next few days, and it was hugely better. For a better, more long-term solution, follow the advice of standing-mat devotees everywhere and get a nice gel mat to stand on, such as the Dandy one I mentioned above. These mats are a bit pricey, but feel so much better.

Knockout 3.0 Release Candidate available

Last week we published the Knockout 3.0 Release Candidate. Hopefully this is the last prerelease version of KO 3.0! The final release should be out in the coming weeks depending on developer feedback.

What’s new?

The biggest chunk of new v3 functionality was already included in the 3.0 Beta release. The beta included a huge overhaul of the binding system that makes it more powerfully extensible. In particular, this included:

  • Node binding and rewriting which lets you define custom binding syntaxes, and

  • Independent and ordered bindings which fixes the most longstanding difficulties with complex combinations of bindings.

See my beta blog post for details and examples.

Since the beta, we’ve focused mainly on performance, stability, and backward compatibility. Of course, we couldn’t resist including a few new features too… So, new in the release candidate is:

  • Array change subscriptions – a super-fast way to find out how an observable array has changed (i.e., which items were just added/deleted/moved) without having to run any differencing algorithm. See the example later.
  • Binding to detached nodes so frameworks built on top of Knockout have an easier time organizing all the DOM fragments they work with in the background.
  • Clearer error reporting if binding hits a problem. After all, nobody enjoys debugging…
  • More helpful handling of arrays in which each entry is an observable (as distinct from observable arrays, which have of course always worked nicely).
  • Performance improvements
    • Computed properties no longer issue change notifications by default if their computed value is definitely unchanged since last time.
    • Reduced by almost half the number of hidden, internal observables that Knockout constructs to manage the state of your bindings.
    • Reduced the stack depth when processing chained observable notifications by four call frames per observable, permitting much longer chains.
  • Bug fixes including HTML-encoding dropdown-list captions, and reinstating the .toJSON function on the output from ko.toJS (which was inadvertently omitted in KO v3 beta).
  • New build system based on Grunt.js to make contributing to Knockout easier. At last all the custom Bash scripts are gone :)
    • Fun fact: as recently as 9 months ago, Knockout still included an MS-DOS batch-file build system. Ah, the magic of %~dp0

Array change subscriptions

Full docs will be available with the final release, but for now, here’s an example of how to receive array change descriptions with KO 3. This will be useful for anybody processing complex chains of observable arrays, or using them for anything besides binding to the UI (which is already automatic).

Whenever you have an observableArray, you can now subscribe to its new arrayChange notifications:

var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);
 
myArray.subscribe(function(changes) {
 
    // For this example, we'll just print out the change info
    console.log(changes);
 
}, null, "arrayChange");

Now if you add/remove/reorder its contents, you’ll be given a concise description of what happened. For example, if you add an item (e.g., changing ['Alpha', 'Beta', 'Gamma'] to ['Alpha', 'Beta', 'Gamma', 'Delta']):

myArray.push("Delta");
// Console output: [{ index: 3, status: 'added', value: 'Delta' }]

Or if you delete an item (e.g., changing ['Alpha', 'Beta', 'Gamma', 'Delta'] to ['Alpha', 'Beta', 'Gamma']):

myArray.pop();
// Console output: [{ index: 3, status: 'deleted', value: 'Delta' }]

Or if you perform a combination of deletions and additions at once (e.g., changing ['Alpha', 'Beta', 'Gamma'] to ['Alpha', 'New value']):

myArray.splice(1, 2, "New value");
// Console output:
// [{ index: 1, status: 'deleted', value: 'Beta' },
//  { index: 1, status: 'added', value: 'New value' },
//  { index: 2, status: 'deleted', value: 'Gamma' }]

Or if you change the order of items (e.g., from ['Alpha', 'New value'] to ['New value', 'Alpha']):

myArray.reverse();
// Console output:
// [{ index: 0, moved: 1, status: 'deleted', value: 'Alpha' },
//  { index: 1, moved: 0, status: 'added', value: 'Alpha' }]

In this last case, notice that as a consumer of this information, you can optionally ignore the moved information and just interpret it as the original Alpha being deleted, and a different Alpha being added to the array’s end. Alternatively you can recognize that the moved information tells you that you can think of the added and deleted items being the same thing just changing position (by matching up the indexes). Either is a valid interpretation – it’s up to you.

So what? I could do this before using ko.utils.compareArrays

Yes, you could, but the benefits of arrayChange are:

  • Performance is O(1) in most cases, i.e., there’s basically no performance implication at all, because for straightforward operations, (push, splice, etc.) KO supplies the change log without running any diff algorithm. KO now only falls back on the diff algorithm if you’ve made an arbitrary change without using a typical array mutation function.
  • The change log is filtered down just to give you the items that actually changed.

Coming soon: A powerful new plugin, built using arrayChange, for efficiently processing chains of maps and filters on observable arrays. Just as soon as I get permission to publish it :)

Please help us by trying it out

We can only release the final Knockout v3 when we have a good level of confidence that it’s as solid and backward-compatible as we expect. To make that happen soon, please try the v3 RC with your existing or new apps and tell us how it goes.

We’ll provide more detailed upgrade information with the final Knockout v3 release, but for now, here a few tips on the (deliberate) breaking changes since v2.3.0:

  • Computed properties now only notify when their output changes. We polled Knockout developers, and the feedback was overwhelmingly in favour of this change. For most developers, this won’t affect you except by making your app perform better. In the rare case where you still want a ko.computed to issue “change” notifications after re-evaluation even if the result is identical to before, you must now configure it using notify like this:

    myComputed.extend({ notify: 'always' });
  • Bindings on a given element now update independently. This is another improvement that KO developers have wanted for a long time. Again, in most cases, it will only affect you by making your app perform better. But if you built custom binding handlers that relied on the undocumented implementation detail that v2.x updates all bindings on the same element at the same time, you may have to stop relying on that detail.

  • optionsCaption now HTML-encodes its output. This is desirable for safety and consistency with every other text-outputting binding (e.g., text). But if you were previously HTML-encoding the string used with optionsCaption, you’ll now have to stop doing so or it will get double-encoded.

Credits

As usual, huge thanks to the team and community members who put the work in to get this implemented and released. Special acknowledgement to Michael Best who designed and built the overhauled binding system (among much more), and to Larry Gordon who instigated the move to Grunt.js. And of course to everyone else who submitted pull requests and issue reports!