async *wrapper* binding

370 views
Skip to first unread message

Michael Best

unread,
Nov 30, 2011, 8:14:19 PM11/30/11
to KnockoutJS
The normal behavior in Knockout is for all bindings on an element to
be linked together. Their dependencies are combined so that an update
to any of the observables in the bindings will update all the
bindings.

This can be a performance issue because updating some bindings is
relatively expensive (options, template). Take following example from
an application I'm developing:

<!-- ko foreach: navigation.pages -->
<div class="body row scroll-y" data-bind="
visible: isCurrent,
attr: { id: 'content' + id },
template: { name: id, 'if': isLoaded, afterRender:
afterRender }">
</div>
<!-- /ko -->

The current page will have isCurrent == true, while all others will
have isCurrent == false. With the above code, whenever the current
page changes, the template is also re-rendered. My initial solution
was to wrap the div in another and move the 'visible' binding to the
outer div:

<!-- ko foreach: navigation.pages -->
<div data-bind="visible: isCurrent">
<div class="body row scroll-y" data-bind="
attr: { id: 'content' + id },
template: { name: id, 'if': isLoaded, afterRender:
afterRender }">
</div>
</div>
<!-- /ko -->

But now with the async wrapper binding (see below), I can make it a
single div again:

<!-- ko foreach: navigation.pages -->
<div class="body row scroll-y" data-bind="
async: { visible: isCurrent },
attr: { id: 'content' + id },
template: { name: id, 'if': isLoaded, afterRender:
afterRender }">
</div>
<!-- /ko -->

The async wrapper binding can be used to isolate bindings that modify
a property of an element and are likely to be updated dynamically,
such as visible, enable, and hasfocus, from bindings that are used to
generate content, such as template, html, and options. See
http://groups.google.com/group/knockoutjs/browse_thread/thread/d58dc5134e7fb1f2
about using this with 'options'.

Here is the source for the binding:

ko.bindingHandlers['async'] = {
'init': function(node, valueAccessor, parsedBindingsAccessor,
viewModel, bindingContextInstance) {
var parsedBindings = valueAccessor();
function makeValueAccessor(bindingKey) {
return function () { return parsedBindings[bindingKey] }
}
var binding, bindingKey;
for (bindingKey in parsedBindings) {
if (!(binding = ko.bindingHandlers[bindingKey])) {
continue;
}
if (!ko.isObservable(parsedBindings[bindingKey])) {
throw new Error('async binding must be used with
observables only');
}
ko.dependentObservable((function(bindingKey, binding){
var isInit = false;
return function () {
if (!isInit && typeof binding["init"] ==
"function") {
var initResult = binding["init"](node,
makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel,
bindingContextInstance);
isInit = true;
}
if (typeof binding["update"] == "function") {
binding["update"](node,
makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel,
bindingContextInstance);
}
}
})(bindingKey, binding), null,
{'disposeWhenNodeIsRemoved': node});
}
}
};

Mark Bradley

unread,
Nov 30, 2011, 9:53:02 PM11/30/11
to knock...@googlegroups.com
I think it might be important to note that if your binding definition
dereferences the observable directly [1] then the async binding will
not work as expected. Not a deal breaker, but a caveat worth
mentioning.

[1]: e.g.

<tag data-bind="text: observableA, async: { visible: observableB() ||
false }"></tag>

The text binding will be evaluated whenever observableB is updated
because as it is processed currently, the observableB will be a
dependency of the whole DO, not the async DO.

--
-barkmadley
sent from an internet enabled device
http://barkmadley.com

Michael Best

unread,
Dec 1, 2011, 8:31:31 AM12/1/11
to KnockoutJS
Mark,

Yeah, there's no easy way around that. I have this in my code to check
that you're binding to an observable:

if (!ko.isObservable(parsedBindings[bindingKey])) {
throw new Error('async binding must be used with observables
only');
}

Of course, this does prevent some bindings that would actually be
okay, so I'm not sure about leaving it there or not. For example:

<tag data-bind="text: observableA, async: { css: { 'classname':
observableB } }"></tag>

would fail the observable check.

-- Michael

rpn

unread,
Dec 1, 2011, 9:22:17 AM12/1/11
to knock...@googlegroups.com
A generic binding for this is a nice idea Michael!

Michael Best

unread,
Dec 1, 2011, 2:50:45 PM12/1/11
to KnockoutJS

Michael Best

unread,
Dec 1, 2011, 3:12:57 PM12/1/11
to KnockoutJS
Another way to use this binding is to create a new binding using this
function:

var createAsyncBinding = function(binding, newname) {
if (typeof newname == 'undefined') {
newname = binding + 'Async';
}
var makeAsyncValueAccessor = function(valueAccessor) {
return function() { var r = {}; r[binding] =
valueAccessor(); return r; };
};
ko.bindingHandlers[newname] = {


'init' : function(node, valueAccessor,
parsedBindingsAccessor, viewModel, bindingContextInstance) {

return ko.bindingHandlers['async']['init'](node,
makeAsyncValueAccessor(valueAccessor),
parsedBindingsAccessor, viewModel,
bindingContextInstance);
}
}
};

Calling the function will create a new binding:

createAsyncBinding('visible'); // will create a visibleAsync
binding

Now the above example can be redone as:

<!-- ko foreach: navigation.pages -->
<div class="body row scroll-y" data-bind="

visibleAsync: isCurrent,

lovedota

unread,
Dec 1, 2011, 9:08:14 PM12/1/11
to KnockoutJS
Could you please post a demo in jsfiddler ! Thanks !

lovedota

unread,
Dec 1, 2011, 9:23:06 PM12/1/11
to KnockoutJS
<!-- ko foreach: navigation.pages -->
<div class="body row scroll-y" data-bind="
async: { visible: isCurrent },

attr: { id: 'content' + id },
template: { name: id, 'if': isLoaded, afterRender:
afterRender }">
</div>
<!-- /ko -->

Could you please give this in the fiddler ! Thanks !

Mark Bradley

unread,
Dec 2, 2011, 12:36:23 AM12/2/11
to knock...@googlegroups.com
After the 1.3 release it would be worth investigating the possibility
of making the binding's as lazy as possible.

I know each binding handler takes a function that will return the
value of it's key-value pair. However this function is created much
too late in the process, as such bindings get "evaluated" and then the
values get wrapped in functions (or not if they are observables, or
something). It would be really great if we could combine the two
different bits of code:

https://github.com/SteveSanderson/knockout/blob/master/src/binding/jsonExpressionRewriting.js#L38

with a transformation that makes it look like this bit of code:

https://github.com/SteveSanderson/knockout/blob/master/src/utils.js#L169

such that this:

'visible: observable'

gets converted into this:

'{ visible: function() { return ( with(sc[0]) { observable; } ); } }'

instead of this:

'(function(sc) { return ( with(sc[0]) { { visible: observable }; } );
})(scopes)'

(I know this isn't exactly a correct characterisations of how knockout
works, however the concept is sound).

On Fri, Dec 2, 2011 at 1:22 AM, rpn <rnie...@gmail.com> wrote:
> A generic binding for this is a nice idea Michael!

--

Reply all
Reply to author
Forward
0 new messages