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});
}
}
};
[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
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
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,
Could you please give this in the fiddler ! Thanks !
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!
--