Prevent knockout from applying bindings to elements that are not visible

722 views Asked by At

I am rendering a large recursive data structure, and am trying to allow users to be able to toggle editing of individual elements within that structure. I am having trouble with performance when applying the bindings due to the editor binding being applied to all elements, even if they are not visible yet.

<div data-bind="visible: isEditable()">
    <textarea data-bind="kendoEditor: { value: name }" > </textarea>
    <button type="button" data-bind="click: toggleEdit(false)">Update</button>
</div>

If you look at the following example, it appears to work correctly. You can click on an element, it will enable an editor, you can click update and the changes will be applied.

jsfiddle 1

However, if you look at the following example where I have added more data, the initial load is very slow due to applying the kendoEditing binding to all elements in the list.

jsfiddle 2

Is there any way to prevent bindings from being applied to elements that are not yet visible?

2

There are 2 answers

0
Roy J On BEST ANSWER

You're doing an awful lot of toggling in advance, because your click bindings should be getting a function, not a snippet of code. This is a common mistake.

To avoid binding a million editors, you can just swap templates in and out rather than having visible and invisible elements:

<ul data-bind="template: { name: 'itemTmpl', foreach: Items }"></ul>
<script id="itemTmpl" type="text/html">
<li>
    <div data-bind="template: isEditable() ? 'editable' : 'notEditable'"></div>
    <ul data-bind="template: { name: 'itemTmpl', foreach: $data.items }"></ul>
</li>
</script>
<script id="notEditable" type="text/html">
    <span data-bind="html: name, click: toggleEdit.bind(null,true)">
    </span>
</script>
<script id="editable" type="text/html">
    <textarea data-bind="kendoEditor: { value: name }"></textarea>
    <button type="button" data-bind="click: toggleEdit.bind(null, false)">Update</button>
</script>

Updated Fiddle

0
Valyok26 On

Roy's answer is probably better, but yes, there is a way to prevent bindings from being applied to descendant elements. For this you need a custom binding with init which returns:

return { controlsDescendantBindings: true };

You can later use

ko.applyBindingsToDescendants(bindingContext, element)

to apply bindings.

For example like this:

ko.bindingHandlers.myVisible = {
    init: function(element, valueAccessor) {
        var visible = ko.unwrap(valueAccessor());
      if (visible) {
        $(element).show();
        $(element).data('bindingsApplied', true);
      } else {
        $(element).hide();
        $(element).data('bindingsApplied', false);
        return { controlsDescendantBindings: true };
      }
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var visible = ko.unwrap(valueAccessor());
        if (visible) {
            if (!$(element).data('bindingsApplied')) {
                ko.applyBindingsToDescendants(bindingContext, element);
                $(element).data('bindingsApplied', true);
            }
            $(element).show();
        } else {
            $(element).hide();
        }
    }
}

Fiddle

Documentation