Knockout 1.3.0 Beta Available
Knockout 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 the JavaScript files, get the minified version (13kb gzipped, for production use) and/or the debug version
- For the source, head over to the GitHub repo
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!