Site Meter
 
 

Category: Knockout

Knockout 2.0.0 released

Here it is at last! Knockout 2.0.0 contains a huge set of improvements since the 1.2.x line. If you’re using KO already, see below for some of the highlights.

New to all this? Now’s a great time to check it out. Knockout is an MVVM library for JavaScript – it makes rich dynamic web UIs easier and cleaner to build. The best place to start learning is with the interactive tutorials.

What just happened?

Credits go to the many community members who contributed pull requests, bug reports, new documentation, and support during this process. These people include aaronpowell, amonat, antis, aredridel, artiomchi, barkmadley, b-dur, bmac, cburgdorf, develop-me, DomenicDenicola, doodlemoonch, dre2901, drewlesueur, ducka, gigi81, GLuKKi, hunterloftis, ifandelse, joeldart, kmalakoff, lcbarcellos, mbest, mtscout6, neonstalwart, owenssam, Quaternion, rniemeyer, RoyJacobs, schinckel, seanami, SimonBartlett, studgeek, tehsenaus, and tiberiuana – and that’s just those who made recent pull requests (apologies for not tracking everyone who contributed other things). rniemeyer wrote a lot of documentation too.

Why is it called 2.0.0? Why not 1.3.0?

For a long time, we were planning this next version to be called 1.3.0. However,

  • Quite a few community members are keen on adopting SemVer-style versioning. 2.0.0 is a good place to start (expermentally) with that versioning convention.
  • It’s such a big set of core changes that if this doesn’t count as 2.0, I guess nothing ever would…

New features

I’m mostly going to copy and paste the “what’s new” list from my earlier blog post about Knockout 1.3 beta, with a few further updates. Besides these things, there are all the usual bugfixes and performance improvements that you’d expect.

1. Control flow bindings

Previously, if you wanted to display a repeating sequence of UI elements (“foreach”), or have a section of the DOM exist only if some viewmodel condition was true (“if”), you’d need to create an entire template to define that. A bit cumbersome for something so simple and commonplace! Now you can use the new control flow bindings – if, ifnot, with, and foreach – to achieve basic, declarative control flow without the need for a template. This can make your code a lot more elegant and to-the-point.

Here’s an example of using “foreach” and “if”:

Tip: Switch to the “result” tab to see the output

The "with" binding changes the binding context to whatever object you specify. This makes it easy to compose many independent view models together – you have a host model that contains references to its children, and then use "with" to bind different sections of the page to different child models. Example:

Of course, the "if", "ifnot", "foreach", and "with" bindings can all be combined and nested arbitrarily.

2. Containerless control flow

What if you want to set up template-less control flow (as in the above example), but without having to use extra container elements just to hold the "foreach", "if", etc., bindings? In that case, you can use the new comment-based control flow syntax like this:

The comment-based control flow syntax works with the "if", "ifnot", "foreach", "with", and "template" bindings. In a large number of cases, this means you can skip string-based templates and use the binding-based control flows instead, which can run much faster depending on what exactly you’re doing.

3. Access to parent binding contexts

Whether you’re using regular nested templates, or nesting the new control flow blocks, sometimes you want to reference properties that exist at the upper levels of binding. There are four new pseudo-variables that you can use in any binding (inside or outside traditional templates):

  • $data – returns the current item
  • $parent – returns the item from the parent binding context
  • $parents – an array containing all the parent binding contexts. $parents[0] == $parent, then $parents[1] is the level above that, and so on.
  • $root – returns the item at the top level of binding (usually your primary view model)

Example:

4. Cleaner event handling

In most cases, data-bind attributes can be kept clean and elegant. But one rough spot in the past has been event handlers, because to pass parameters, you were generally guided to use function literals. For example:

<button data-bind="click: function() { viewModel.items.remove(this) }">Click me</button>

Many of you rightly pointed out that this is pretty ugly. It’s much nicer if bindings can be kept minimal and declarative! To fix this, KO 2.0 offers two alternative event handling syntaxes, both of which are better.

Firstly, event handlers now receive the current model value as their first parameter. So, you can implement “remove-from-list” as follows, with no inline function literals:

The other option is to use jQuery event delegation to catch events and invoke functions on your viewmodel. This is made possible by the ko.dataFor and ko.contextFor helpers, which tell you which part of your viewmodel a given event relates to. Here’s the preceding example, but now using event delegation:

5. Binding providers (and hence external bindings)

Most KO developers find the default data-bind attributes provide a very convenient, easy-to-maintain way of declaratively associating viewmodels with views. But if you would prefer to store your binding configuration elsewhere, the new binding providers API is an extensiblity point that allows alternative configuration mechanisms to be plugged in.

For example, here’s one possible alternative way that you could configure bindings. Notice the absence of data-bind attributes – I’m using a binding provider to set up the binding configuration in JavaScript:

To be clear, binding providers are a general extensibility mechanism, not any specific configuration format. The above example is just one possible way it can be used to define bindings in JavaScript. I’m looking forward to seeing suggestions from the community about how this extensibility can be used, and for what the most elegant and convenient way of defining bindings might be. I know some of you have already suggested externalising bindings (e.g., Brandon Satrom’s knockout.unobtrusive plugin); this new API will make it easier to implement custom binding mechanisms. Bring on more plugins!

6. Throttling

Note: ko.computed is Knockout 2.0’s new name for ko.dependentObservable – it’s easier to say, type, and explain. This doesn’t affect backward compatibility, though – ko.dependentObservable still works, because it references the same function instance as ko.computed at runtime. You don’t have to change your existing code if you don’t want.

Occasionally, it’s desirable to limit how fast observables and computed observables update. For example, you might be using a ko.computed to invoke Ajax requests whenever a set of observables change. Sometimes you might want to change multiple underlying observables, but only have that ko.computed re-evaluate once, after all the changes (so that it only fires one Ajax request). Effectively, you want a kind of atomic update.

This is now pretty easy to do: you can apply an extender called "throttle" either to observables or to computed observables. For example:

// Will not notify changes faster than once per 500ms 
var myObservable = ko.observable("initial value").extend({ throttle: 500 });
 
// Will not re-evaluate *or* notify changes faster than once per 500ms 
var myComputed = ko.computed(function() { 
    // Evaluation logic goes here, usually referencing other observables 
    return 123;
}).extend({ throttle: 500 });

The way this works is that, when throttled, notifications and evaluations are done asynchronously, and don’t occur at all until they stop being triggered for the specified throttle timeout duration.

In the case of making a computed value only update once even if there are multiple synchronous changes to its dependencies, you can use a very short timeout (for example, 1ms), and then it will re-evaluate as soon as possible after any series of synchronous changes to its dependencies. Example:

Breaking changes

There are two changes that might realistically affect existing 1.2.x application code. Both are for good reasons and are important for the long-term health of the project…

  • Event handlers now receive your model object as their first parameter, and the DOM event object as the second parameter. Previously, they only received the DOM event object as the first parameter. So, if you are using the “click” or “event” bindings and are specifically catching and using the DOM event object, you’ll need to change your handler’s signature from this:

myViewModel.myEventHandler = function(evt) { /* do something with evt */ }

… to this:

myViewModel.myEventHandler = function(data, evt) { /* do something with evt */ }

This will not affect any event handlers that don’t specifically receive and process the “event” parameter. We chose to make this change because it significantly simplifies event handling in the majority of cases. Most developers have been able to upgrade to 2.0.0 beta without changing their applications at all.

  • KO 2.0.0 drops support for very old versions of jquery-tmpl. If you need to use string-based templating (and most often you won’t now that KO 2.0.0 has native control flow bindings), get jquery-tmpl 1.0.0pre. But also consider dropping the string-based templating, and switching to control flow bindings instead, as jquery-tmpl is no longer in active development.

Summary

Try the updated interactive tutorials, update your existing KO applications, have a great Christmas/vacation/newyear Smile

Full-height app layouts: Navigation and History

This is the third in a series of posts about web app layouts, i.e., giving your web-based application a UI comparable to a desktop or mobile/tablet app. Posts so far:

  1. Layout basics – A CSS technique for slicing up the browser window into arbitrarily nested panes both horizontally and vertically. You know, like a proper application, and not like an infinite-height document…
  2. Animated transitions – How to put more than one content block into a given pane, and then switch between them with hardware-accelerated animations
  3. This post – Keeping track of what content has appeared in each pane, so the visitor can navigate back and forwards. And supporting deep linking. And injecting new panes dynamically.

Disclaimer: To be clear, this post series just represents my own experiments in lightweight, flexible web app layouts and is not an official part of any Microsoft technology stack. No guarantees or warranties or SLAs, blah blah blah.

The goal

It’s very common on mobile/tablet-like UIs for users to navigate through some structure of content within a given pane. For example, in a tablet-like app, you’ll often want to have a left-hand pane representing navigation through a hierarchy, with the main section of the screen representing the currently selected item or folder:

image

So, you’ll want to be able to:

  • Keep track of where the user has been, letting them go back and forwards, with each pane able to maintain its own independent history.
  • Perform smooth animated transitions within each pane when any navigation event occurs.
  • Support deep-linking to arbitrary locations in your content structure
  • Fetch and render content dynamically

Basic navigation

Continuing from previous posts that introduced pane hierarchies and panes.js, it’s pretty easy to keep track of where a visitor has been. First you might set up a basic pane layout with a header/body/footer like this:

<div class="page">
    <div class="header row">
        Header will go here
    </div>
    <div class="body row">
        Body contents will go here
    </div>
    <div class="footer row">
        My footer. Could put icons here.
    </div>
</div>

… and then put multiple panes into the body row:

<div class="body row">
    <div id="location-continents" class="pane">
        <h3>Continents</h3>
        <ul>
            <li><a href="#america">America</a></li>
            <li><a href="#europe">Europe</a></li>
        </ul>
    </div>
 
    <div id="location-america" class="pane">
        <h3>Countries in the Americas</h3>
        <ul>
            <li><a href="#canada">Canada</a></li>
            <li><a href="#usa">USA</a></li>
        </ul>
    </div>
 
    <div id="location-canada" class="pane">
        <h3>Cities in Canada</h3>
        <ul>
            <li>Vancouver</li>
            <li>Toronto</li>
            <li>Edmonton</li>
        </ul>
    </div>
 
    <!-- ... etc ... -->
</div>

Now you can begin history tracking by adding a bit of JavaScript to instantiate a PaneHistory object and navigate to an initial pane:

// Navigation
var paneHistory = new PaneHistory();
paneHistory.navigate("location-continents");

… and perform pane navigations each time the visitor clicks on one of the links:

$("a").click(function (evt) { 
    var dest = (evt.srcElement || evt.target).href.split("#")[1]; 
    paneHistory.navigate("location-" + dest); 
    return false; 
});

This code will intercept clicks on the links, figure out which pane they are trying to get to, and then animate that destination pane sliding smoothly into the body area from the right.

If you also want to let the visitor go “back” through the pane’s history stack, add a button perhaps into the page header:

<div class="header row">
    <p><button class="goBack">&lt; Back</button></p>
</div>

… and handle clicks by instructing the PaneHistory instance to perform a reverse navigation animation:

$(".goBack").click(function () {
    paneHistory.back();
});

That’s it. Try it out. Live example:

Also: Run full screen (e.g., to try it on a phone)

Supporting the back/forward buttons and deep-linking

If your app will be deployed to the web (as opposed to using something like PhoneGap to package for an appstore), then you will almost certainly want to respect the browser’s native back/forward buttons and allow deep-linking to specific locations in your virtual navigation system.

There are many JavaScript libraries for working with browser history and the HTML5 pushState feature. Currently my favourite is history.js (license: New BSD) by Benjamin Lupton, because of its robustness, great pushState support, support for older browsers, and because it has no dependencies.

panes.js integrates with history.js, so once you’ve added a reference to history.js, you can start using UrlLinkedPaneHistory instead of PaneHistory. Specify the name of one or more URL parameters that the pane will use to represent what data it is showing. In this case, I’ll call my parameter “location”:

var paneHistory = new UrlLinkedPaneHistory({
    params: { location: 'continents' }
});

Then, update your paneHistory.navigate call so that it specifies which URL parameter is to be updated (in this case, my only parameter, “location”):

$("a").click(function (evt) {
    var dest = (evt.srcElement || evt.target).href.split("#")[1];
    paneHistory.navigate({ location: dest });
    return false;
});

And finally, since it no longer makes sense for “back” clicks to affect only a single pane (the URL history is global), update your “back” button handler so that it performs a global browser “back” navigation:

$(".goBack").click(function () {
    History.back();
});

… and you’re done. Now if you run the follow demo full-screen (click the link below the fake phone UI), you’ll see the URL update as you navigate around, and you can use the back/forward buttons (still getting the animated transitions), and can bookmark or refresh the page without losing your position. If your browser supports HTML5 pushState, for example recent versions of Chrome and Firefox, then your URLs will be updated with real querystring parameters as you go. If your browser doesn’t support pushState, history.js will gracefully degrade to using a URL hash.

Try it:

Note: If you want to be able to see the URL updating,
and to use the back/forward buttons, view this example full screen

Dynamically generating panes

If you do a view HTML source on either of the two previous demos, you’ll see that there’s a long list of pre-prepared <DIV> elements – one for each pane that you might visit (Europe, Canada, Vancouver, Toronto, etc….). That’s OK if your app only has a small and finite set of visitable panes (e.g., because those panes represent a fixed number of tabs), but it’s awkward if there are a lot of panes, and useless if the number is effectively infinite because panes represent navigation through data in some large external database.

Fortunately, this is easy to fix. Because panes.js doesn’t modify your DOM structure in any way, and requires no initialisation step to start using newly-inserted DOM elements, it composes perfectly with any external mechanism for updating the DOM. In this example, I’m going to use Knockout.js to inject new panes dynamically as data is fetched from some external source.

The trick here is to make the UrlLinkedPaneHistory a property of your Knockout view model. For example:

function AppViewModel() {
    this.paneHistory = new UrlLinkedPaneHistory({
        params: { location: null },
 
        // Making the set of history entries observable, so we can dynamically generate corresponding DIVs
        entries: ko.observableArray([]),
 
        // Each time the visitor navigates forwards, this will be called to fetch data for the new pane
        loadPaneData: function (params, callback) {
            // Here I'm loading data from some external source, asynchronously
            locationInfoService.getLocationInfoAsync(params.location, callback);
        }
    });
};
 
ko.applyBindings(new AppViewModel());

(Note: do a “view source” on the live example below if you want to see the finished code and what JS libraries you must reference to make all this work.)

Now the set of history entries is observable, we can ask Knockout to generate corresponding DIVs dynamically, by using a “foreach” binding:

<div class="body row" data-bind="foreach: paneHistory.entries">
    <div id="location-continents" class="pane scroll-y" data-bind="attr: { id: paneId }">
        <h3 data-bind="text: paneData.name"></h3>
    </div>
</div>

Notice that Knockout will assign an ID property to each pane <DIV> corresponding to the navigation parameters, so it can associate navigation events with the <DIV> you want to slide into view.

Of course, you don’t just want to display the “name” property of each item you’re visiting – you want to generate some more UI for each pane. Let’s generate a list of child locations that the user can navigate to:

<div class="body row" data-bind="foreach: paneHistory.entries"> 
    <div id="location-continents" class="pane scroll-y" data-bind="attr: { id: paneId }"> 
        <h3 data-bind="text: paneData.name"></h3> 
        <ul data-bind="foreach: paneData.children"> 
            <li data-bind="text: name, 
                            click: $root.navigate, 
                            css: { navigable: childLocations.length }"></li> 
        </ul> 
    </div> 
</div>

Each of the <li> elements there will try to invoke a viewmodel method called “navigate” when you click it. Implement that by adding a “navigate” method to your viewmodel:

function AppViewModel() {
    // Rest of class unchanged
 
    this.navigate = function (evt) {
        var destination = ko.dataFor(evt.srcElement || evt.target);
        if (destination.childLocations.length)
            this.paneHistory.navigate({ location: destination.id });
    } .bind(this);
};

Now the visitor will be able to navigate forwards through the hierarchy of locations. What about navigating backwards too? You can put a “back” button into the header pane, and use a Knockout binding to make its text update to show the name of parent location (e.g., “< Europe”):

<div class="header row">
    <p data-bind="with: paneHistory.currentData().parent">
        <button data-bind="click: $root.navigate">&lt; <span data-bind="text: name"></span></button>
    </p>
</div>

Done. Your visitor can now navigate through an arbitrarily deep hierarchy, with the data being pulled from the server as needed and dynamically rendered on the client. The browser’s back/forward buttons still work, deep linking works, and there are smooth animated transitions. And you wrote about 15 lines of JavaScript Smile. Have a go with it:

Note: If you want to be able to see the URL updating,
and to use the back/forward buttons, view this example full screen

What’s next?

Well, what are you interested in? Possible next posts in this series:

  • Using all this stuff to build a complete application with meaningful functionality
  • Making this kind of Single Page Application (SPA) work offline via HTML5 offline support
  • Packaging and selling such apps on mobile appstores via PhoneGap/Callback
  • Data access: editing collections of entities, and letting the user track, synchronise, and revert their changes

If you’re building single page applications, whether for mobile or desktop or both, please let me know what sort of technologies you’re using, what challenges you face, and what kinds of features you’d like to see baked into the ASP.NET/MVC stack.

Knockout 1.3.0 Beta Available

iconKnockout version 1.3.0 is coming soon, and it’s going to be a huge release with a big stack of features that many of you have asked for.

The key theme is developer happiness – the new features will let you get more done with less code, eliminate some previous limitations, and offer more power to extend and customize how things work.

New to Knockout? If you’ve never used it before, don’t start with this post – start with the interactive tutorials.

The 1.3 code is now stable and feature-complete, so it’s time for a beta release. You can get it from…

For Visual Studio users, the NuGet package ID is knockoutjs, and this latest version is 1.2.9.0 (reserving version 1.3.0 for the final 1.3 release).

So, what’s new? Well, full documentation for the new features is still to be written, but here’s a brief rundown of the main enhancements.

1. Control flow bindings

Previously, if you wanted to display a repeating sequence of UI elements (“foreach”), or have a section of the DOM exist only if some viewmodel condition was true (“if”), you’d need to create an entire template to define that. A bit cumbersome for something so simple and commonplace! Now you can use the new control flow bindings – if, ifnot, with, and foreach – to achieve basic, declarative control flow without the need for a template. This can make your code a lot more elegant and to-the-point.

Here’s an example of using “foreach” and “if”:

Tip: Switch to the “result” tab to see the output

The "with" binding changes the binding context to whatever object you specify. This makes it easy to compose many independent view models together – you have a host model that contains references to its children, and then use "with" to bind different sections of the page to different child models. Example:

Of course, the "if", "ifnot", "foreach", and "with" bindings can all be combined and nested arbitrarily.

2. Containerless control flow

What if you want to set up template-less control flow (as in the above example), but without having to use extra container elements just to hold the "foreach", "if", etc., bindings? In that case, you can use the new comment-based control flow syntax like this:

The comment-based control flow syntax works with the "if", "ifnot", "foreach", "with", and "template" bindings. In a large number of cases, this means you can skip string-based templates and use the binding-based control flows instead, which can run much faster depending on what exactly you’re doing.

3. Access to parent binding contexts

Whether you’re using regular nested templates, or nesting the new control flow blocks, sometimes you want to reference properties that exist at the upper levels of binding. There are four new pseudo-variables that you can use in any binding (inside or outside traditional templates):

  • $data – returns the current item
  • $parent – returns the item from the parent binding context
  • $parents – an array containing all the parent binding contexts. $parents[0] == $parent, then $parents[1] is the level above that, and so on.
  • $root – returns the item at the top level of binding (usually your primary view model)

Example:

4. jQuery-style event handling

In most cases, data-bind attributes can be kept clean and elegant. But one rough spot in the past has been event handlers, because to pass parameters, you were generally guided to use function literals. For example:

<button data-bind="click: function() { viewModel.items.remove(this) }">Click me</button>

Many of you rightly pointed out that this is pretty ugly. It’s much nicer if bindings can be kept minimal and declarative! To fix this, KO 1.3 lets you replace that with:

<button class="remove-item">Click me</button>

… and then in your JavaScript, add this (if you’re using jQuery):

$(".remove-item").live("click", function() { 
    viewModel.items.remove(ko.dataFor(this));
});

There are two new helper functions that make this possible:

  • ko.dataFor(domElement) – returns the data item associated with a particular DOM element
  • ko.contextFor(domElement) – returns the entire binding context associated with the DOM element, so you can retrieve its $parent property, etc.

Example:

5. Binding providers (and hence external bindings)

Most KO developers find the default data-bind attributes provide a very convenient, easy-to-maintain way of declaratively associating viewmodels with views. But if you would prefer to store your binding configuration elsewhere, the new binding providers API is an extensiblity point that allows alternative configuration mechanisms to be plugged in.

For example, here’s one possible alternative way that you could configure bindings. Notice the absence of data-bind attributes – I’m using a binding provider to set up the binding configuration in JavaScript:

To be clear, binding providers are a general extensibility mechanism, not any specific configuration format. The above example is just one possible way it can be used to define bindings in JavaScript. I’m looking forward to seeing suggestions from the community about how this extensibility can be used, and for what the most elegant and convenient way of defining bindings might be. I know some of you have already suggested externalising bindings (e.g., Brandon Satrom’s knockout.unobtrusive plugin); this new API will make it easier to implement custom binding mechanisms. Bring on more plugins!

6. Throttling

Occasionally, it’s desirable to limit how fast observables and dependent observables update. For example, you might be using a dependentObservable to invoke Ajax requests whenever a set of observables change. Sometimes you might want to change multiple underlying observables, but only have that dependentObservable re-evaluate once, after all the changes (so that it only fires one Ajax request). Effectively, you want a kind of atomic update.

This is now pretty easy to do: you can apply an extender called "throttle" either to observables or to dependent observables. For example:

// Will not notify changes faster than once per 500ms 
var myObservable = ko.observable("initial value").extend({ throttle: 500 });
 
// Will not re-evaluate *or* notify changes faster than once per 500ms 
var myDependentObservable = ko.dependentObservable(function() { 
    // Evaluation logic goes here, usually referencing other observables 
    return 123;
}).extend({ throttle: 500 });

The way this works is that, when throttled, notifications and evaluations are done asynchronously, and don’t occur at all until they stop being triggered for the specified throttle timeout duration.

In the case of making a dependent observable only update once even if there are multiple synchronous changes to its dependencies, you can use a very short timeout (for example, 1ms), and then it will re-evaluate as soon as possible after any series of synchronous changes to its dependencies. Example:

This is all I have time to write about just now, but there are also some other new extensibility features I haven’t mentioned. I’ll follow up with more as soon as I can.

Backward compatibility notes

This beta version of Knockout 1.3 should work with your existing applications, with the following exceptions:

  • It has dropped compatibility with old versions of jquery.tmpl. If you’re using a version of jquery.tmpl older than 1.0.0pre, you’ll need to upgrade. You can find it at https://github.com/jquery/jquery-tmpl.
  • The API for registering custom template engines has changed, so any existing custom ones will need to be updated. The change makes it much easier to define custom view engines (you don’t have to worry about "rewriting" any more). I’ll write a separate post about this soon.
  • Since the binding algorithm has been enhanced in many ways, any third-party plugins that specifically try to alter how bindings work may not work immediately (for example, the "namespaces" plugin). I’ll be talking to the creators of those plugins to ensure both KO and the plugins are updated to work together properly. Apologies for any inconvenience! The new extensibility APIs should make it much easier to implement these kinds of plugins now anyway.

Please try it out and let me know what you think of the new features!