3

I have a Knockout custom binding handler that I want to call the foreach bindings functionality on within it and then call a callback function afterwards. I keep getting a "Uncaught Error: You cannot apply bindings multiple times to the same element. " error now as I try to do this.

My custom binding is pretty simple (typescript):

/// <reference path="knockout.d.ts" />
ko.bindingHandlers["postForeach"] = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        if (!allBindingsAccessor().postForeachCallback)
            throw "Callback not defined for postForeach binding!";

        //call foreach init functionality
        ko.bindingHandlers['foreach'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            //call foreach update functionality
            ko.bindingHandlers['foreach'].update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
            //call callback
            allBindingsAccessor().postForeachCallback();
    }
};

Is there something I am missing in constructing this?

Thanks!

EDIT:

Callback Function

self.populateMainContentWindow = function () {
                    var dataTable = $(this.tableId).dataTable();
        dataTable.fnDestroy();

                // create the datatable
                        var actualTable = this.jQuery(this.tableId);
        if (actualTable.length == 0) {
            return false;
        }

        // create the data table with options
        var newDataTable = actualTable.dataTable(this.options);

        // always set the width afterwards
        actualTable.css("width", "100%");
            };

Data Bind Signature (which is within a 'with' binding):

postForeach: array, postForeachCallback: $parent.viewModel().populateMainContentWindow
nobody
  • 7,803
  • 11
  • 56
  • 91
  • I haven't used TypeScript, which you appear to be using, but it looks like your binding is calling it's own init again. Have you tried putting a console.log in there to see if it is calling it's own init? – PW Kad Sep 14 '13 at 01:26
  • Please create a JSFiddle which repros your issue! Your code with Michael answer works fine: http://jsfiddle.net/94epu/ – nemesv Sep 16 '13 at 07:38
  • What is `this` in `populateMainContentWindow`? Based on the code, it would be the binding object returned from `allBindingsAccessor`, which won't have `tableId`, `jQuery`, or `options`. So that function shouldn't be working. – Michael Best Sep 16 '13 at 20:15
  • If your binding is `foreach: BatchDefinitions, postForeach: { postForeachCallback: $parent.etlViewModel().populateMainContentWindow }`, it won't work. Your other version, `postForeach: BatchDefinitions, postForeachCallback: parent.etlViewModel().populateMainContentWindow` looks like it should work. – Michael Best Sep 16 '13 at 20:17

3 Answers3

6

Knockout uses the return value of init to determine whether it should process the element's descendants. You can either just return the value of the foreach.init function or specifically return { controlsDescendantBindings: true } from your init function:

init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    if (!allBindingsAccessor().postForeachCallback)
        throw "Callback not defined for postForeach binding!";

    //call foreach init functionality
    return ko.bindingHandlers['foreach'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},

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

Michael Best
  • 16,623
  • 1
  • 37
  • 70
  • Should there be something special as well for the update call? – nobody Sep 14 '13 at 03:44
  • Doesn't solve the issue...I still get the same problem "Uncaught Error: You cannot apply bindings multiple times to the same element." – nobody Sep 16 '13 at 07:23
  • 1
    Based on the information and code you've provided, this will solve the problem. Maybe there's another problem somewhere elese. Can you also give us the code for the "postForeachCallback" function and the Html code where you've got the "postForeach" binding? – Michael Best Sep 16 '13 at 11:03
  • @MichaelBest This answer really helped me out, thanks! I should mention for others that it worked for my issue with the `if` binding as well. All I needed to do was return the result of the `init` call from my overriden binding. Also, remember to pass **ALL** of the parameters to the parent binding. – jbarz Mar 04 '14 at 00:46
2

Variant 1
I think your current binding looks like

data-bind="foreach: someArray, postForeach: ..."  

You can setup value for foreach binding inside postForeach binding. e.g:

data-bind="postForeach : 
           {postForeachCallback : function()
                                  {
                                    alert('After')
                                  },
           foreach: someArray}" // this is your old foreach  

binding:

ko.bindingHandlers["postForeach"] = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        if (!valueAccessor().postForeachCallback)
            throw "Callback not defined for postForeach binding!";

        //call foreach init functionality
        return ko.bindingHandlers['foreach'].init(element, valueAccessor().foreach, allBindingsAccessor, viewModel, bindingContext);
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            //call foreach update functionality
            ko.bindingHandlers['foreach'].update(element, valueAccessor().foreach, allBindingsAccessor, viewModel, bindingContext);
            //call callback
            valueAccessor().postForeachCallback();
    }
};  

JSFiddle DEMO

Variant 2
This varian view model (innerViewModel) inside view model. This view model is binded with with binding.
Now data-bind lookls like

data-bind="postForeach : true, postForeachCallback : $parent.postCallback, foreachEx: values"  

View looks like

<div data-bind="with: innerViewModel">
   <ul data-bind="postForeach : true, postForeachCallback : $parent.postCallback, foreachEx: values">
       <li data-bind="text: val"></li>
   </ul>
</div>

JSFiddle DEMO

Ilya
  • 29,135
  • 19
  • 110
  • 158
  • My data-bind looks just like "data-bind="postForeach: array, postForEachCallback: function(){ definedFunction() }", even after changing my data bind to match what you have I get the same error. – nobody Sep 16 '13 at 16:10
  • it is within a 'with' binding if that makes any difference – nobody Sep 16 '13 at 16:18
  • @BenNelson second variant with `with` binding is added – Ilya Sep 16 '13 at 17:14
  • still doesn't work...i get a 'undefined is not a function' error – nobody Sep 16 '13 at 17:51
1

It doesn't look like it has anything to do with the code you posted. Rather, it looks like there are multiple calls to applyBindings, and two of them are hitting the same element.

Example: http://jsfiddle.net/tlarson/bFKuL/ (look in the console for the error)

var vm = {
    Name: "George"
}
ko.applyBindings(vm);
ko.applyBindings(vm);

Markup:

<div data-bind="text:Name"></div>

To get around this, don't call applyBindings on the same element twice. To help you figure out exactly how to do this with your code, we'd need to see more of your code, such as a fiddle that demonstrates the problem.

CodeThug
  • 3,054
  • 1
  • 21
  • 17