Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout custom bindingHandler cannot applyBindings on appended elements

I am trying to use a custom bindingHandler that adds something like a template and so needs its own model. Just for information (but not required to understand the problem): the real goal is to manage several components that are located inside the DIV that has this bindingHandler.

However, I am unable to create a custom context BUT keep KO from trying to bind the elements I created (and already applied binding on it with the custom bindingHandler).

All I get is the typical error: Uncaught Error: You cannot apply bindings multiple times to the same element.

What happens?

What I guess is that:

  • Knockout is doing the first applyBinding and search for the binding handlers on elements within it

  • It does the initialization process of it

  • The initialization create a new context with the applyBindings on new elements

  • After the completion of that operation, knockout seems to not take into account "allowBindings:false" on created elements (and elements that are already bound) and try to bind twice the things...

The code (jsfiddle)

http://jsfiddle.net/darknessm0404/tB6Zv/3/

<div id="view">
    viewModel content.
    <p data-bind="text:data"></p>
    <div data-bind="customComponent:{}">
        <p>Need to have is own model. Another element is added while the constructor of the viewModel is instancied.</p>
    </div>
</div>

 

ko.bindingHandlers.allowBindings = {
    init: function (elem, valueAccessor) {
        // Let bindings proceed as normal *only if* my value is false
        var shouldAllowBindings = ko.unwrap(valueAccessor());
        return { controlsDescendantBindings: !shouldAllowBindings };
    }
};

ko.bindingHandlers.customComponent = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            // Append new element
            var $component = $('<div data-bind="allowBindings:false">customComponent content: <span data="text:data"></span><div>').appendTo($(element));
            // Apply bindings on newly created element
            console.log('2.A. customComponent -> applyBindings -> started');
            ko.applyBindings({
                data: 'COMPONENT MODEL DATA'
            }, $component[0]);
            console.log('2.A. customComponent -> applyBindings -> completed');
        }
};

console.log('1.A. view -> applyBindings -> started');
ko.applyBindings({
    data: 'VIEW MODEL DATA'
}, $('#view')[0]);
console.log('1.B. view -> applyBindings -> completed');

How to make it work by saying to knockout to prevent from binding these already-bound items?

How to force knockout to NOT parse these items that are already bound? I tried to add allowBindings:false to the DIV containing the "customComponent:...", it still initializes but I'm not able to get the model.data property out of it.


Solution

Here's a part of the code I've used. I simply had forgotten to add return { controlsDescendantBindings: true }; to prevent KO to apply bindings on the created children elements.

It may not compile directly but the idea is the same: binding a new childContext on a new element created by the binding, and initializing siblings of that item with the current binding context.

With that, I'm able to manage components within a group (div), these components can access to the parent model (code not shown here, it's done via $.closest and storing things with $.data) and so ask themselves to be appended to the "panel.list" referred by the template.

 init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        //...
        // Template creation
        var $template = $('<ul></ul>').prependTo($(element));

        Quadratus.ko.dataBind($template, {
            foreach: 'panel.list'
        });
        $('<li><span data-bind="text:\'hello\'"></span></li>').appendTo($template);

        ko.applyBindings(bindingContext.createChildContext(model), $template[0]);

        $template.siblings().each(function () {
            // Apply current bindingcontext on siblings
            ko.applyBindings(bindingContext, this);
        });
 return { controlsDescendantBindings: true };
like image 727
Micaël Félix Avatar asked May 05 '26 17:05

Micaël Félix


1 Answers

Return { controlsDescendantBindings: true } from your customComponent init function.

See http://knockoutjs.com/documentation/custom-bindings-controlling-descendant-bindings.html

Here is an update to your fiddle: http://jsfiddle.net/tB6Zv/4/

Besides adding the controlsDescendantBindings return value, I also made these changes:

  • Removed allowBindings: false
  • Fixed data="text: data" which should have been data-bind="text: data"
  • Changed the ko.applyBindings for the new elements to ko.applyBindingsToDescendants, which is more commonly used in this situation
like image 169
Joe Daley Avatar answered May 08 '26 08:05

Joe Daley



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!