6

I'm creating a Knockout binding that will work as a wrapper around a jQuery widget. This widget applies event handlers to child elements. Unfortunately, the widget's event handling is applied directly to the child elements, not delegated. The issue is that I have a foreach binding on the same element, but I need the custom binding to be applied after the foreach binding is applied.

Obviously, the right thing is to fix the jQuery plugin, but this isn't an option at this time. I'm wondering if there are any good workaround options for me. Is there way, for example, to do any of the following?

  1. Detect whether a particular binding has been applied
  2. Affect the ordering of binding application
  3. Safely force another binding to take place

Update:

One aspect I should mention is that this custom and foreach binding reside in a template. Therefore, solutions that directly modify the DOM won't work for me since it will actually modify the template.

Jacob
  • 77,566
  • 24
  • 149
  • 228

3 Answers3

7

Add an after property on your bindingHandler with an array of dependencies

ko.bindingHandlers.myHandler = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // Actual code
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // Actual code
    },
    after:['foreach']
};
Matt Lacey
  • 8,227
  • 35
  • 58
DrSammyD
  • 880
  • 12
  • 32
3

If your binding is dependant on foreach binding, why not call it from your custom binding? Then you don't even need to supply it in the data-bind attribute. I helped another SO user the other day, check how I call the options binding from within the custom binding

http://jsfiddle.net/w9bsc/42/

ko.applyBindingsToNode(element, { options: valueAccessor(), optionsCaption: caption, optionsText: optionsText  }, viewModel);
Anders
  • 17,306
  • 10
  • 76
  • 144
  • Thanks for your answer. I actually tried what you did (using `applyBindingsToNode`), and it appears to work. One complication I failed to mention earlier was that my custom binding is applied to a `text/html` template. After doing `applyBindingsToNode` with a `foreach`, the child nodes were created, but they were permanently inserted into the template's DOM, not the template instance's DOM, if you know what I mean. – Jacob Dec 25 '12 at 00:15
  • You should use a template binding for that not a foreach, but maybe thats what you did? – Anders Dec 25 '12 at 00:28
  • I tried both. Either way, `applyBindingsToNode` mutated the DOM inside of the template. – Jacob Dec 25 '12 at 00:32
  • Then you are doing something wrong, the template binding should only use the script tag to get the template, it should rendered in the tag that the template binding is declared on – Anders Dec 25 '12 at 10:55
  • It would be, but not if you call `applyBindingsToNode` directly. – Jacob Dec 25 '12 at 20:34
  • To clarify, my template used a custom binding that references another template. Therefore, when the custom binding is processed on the first template, the element node passed in is an element within the template. Therefore, `applyBindingsToNode` modifies the template itself (not what I want). – Jacob Dec 25 '12 at 20:37
  • Its a bit hard to understand what the problem is, but here is a custom binding inside a template that renders another template, I then use some jquery on that element. As you can see it does not alter the template but the element http://jsfiddle.net/BRrjA/ – Anders Dec 25 '12 at 22:46
  • Here's some demonstrations. The original problem: http://jsfiddle.net/AByXF/3/ (the click handlers are not applied because the elements aren't generated yet). A sample of how applyBindingsToNode modifies the template DOM: http://jsfiddle.net/TYzZ5/2/ (notice how the second time the template is instantiated, there are JS errors because the bindings of the items generated from the second template the first time are present). – Jacob Dec 26 '12 at 08:36
  • http://jsfiddle.net/AByXF/4/ http://jsfiddle.net/TYzZ5/3/ controlsDescendantBindings must be set to true otherwise KO will try to bind the child node directly, when set to true its up to the foreach binding to bind it correctly – Anders Dec 26 '12 at 12:19
2

I found a workaround, but it's more of a hack than I like. I'll still wait for better answers.

What I did was simply this:

ko.bindingHandlers.myHandler = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        if (allBindingsAccessor().foreach) {
            setTimeout(doInit, 1);
        } else {
            doInit();
        }

        function doInit() {
            bindingContext.initializedMyHandler = true;
            // Actual code
        }
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        if (bindingContext.initializedMyHandler) {
            doUpdate();
        } else {
            setTimeout(doUpdate, 1);
        }
        function doUpdate() {
            // Actual code
        }
    }
};

Basically I just defer execution using a timeout. That way, the rest of the binding handlers will execute first.

Jacob
  • 77,566
  • 24
  • 149
  • 228