AlignedDev
AlignedDev

Reputation: 8233

Knockout.js v2.3.0 error "You cannot apply bindings multiple times to the same element"

I've just upgraded to Knockout.js 2.3.0 and now I'm getting this error:

You cannot apply bindings multiple times to the same element.

which I wasn't getting in 2.2.1.

I'm getting a partial view from my MVC controller and adding it to the page after clicking on an href. The error happens the second time I click on the link to get the partial view. I'm doing this multiple times.

Is there a way to clear this out and avoid the new error thrown?

Here's my code:

$.get(url + "GetAssignedCompaniesView?layoutId=" + layoutId + "&noCache=" + new Date().getMilliseconds(), function (result) {
    $("#editAssignedPartial").html($(result));
    showEditAssignedArea(true);
    $(window.document).ready(function () {
        // error is thrown here
        ko.applyBindings(self, window.document.getElementById("editAssigned"));
        $("#layoutId").attr("value", layoutId);
        updateTypeHiddenElement.attr("value", "companies");
    });
});

Here's my HTML:

<div id="area1">
    <!-- grid here with links -->
</div>
<div id="editAssignedPartial"></div>
$(document).ready(function () {
    'use strict';
    var vm = new Vm();
    ko.applyBindings(vm, document.getElementById("area1"));
});

Upvotes: 79

Views: 121488

Answers (13)

Muflix
Muflix

Reputation: 6798

ko.cleanNode($("#modalPartialView")[0]);
ko.applyBindings(vm, $("#modalPartialView")[0]);

That works for me, but as others note, the cleanNode is an internal ko function, so there is probably a better way.

Upvotes: 0

javad hemati
javad hemati

Reputation: 252

I had the same problem and I solved it like this:

var vm = new MessagesViewModel()
ko.applyBindings(vm)

function ShowMessagesList() {
   vm.getData("MyParams")
}

setInterval(ShowMessagesList, 10000)

Upvotes: 0

Neoraptor
Neoraptor

Reputation: 962

In my case, I was adding to a non existing element, or, I was adding bindings to an element that maybe exists, but it's parent did not. Similar to this:

var segDiv =  $("#segments"); // did not exist, wrong id
var theDiv = segDiv.html("<div></div>");

ko.applyBindings(someVM, theDiv);

As far as I can tell, this error seems a bit overloaded in the sense that it will fire on a lot of different errors that can happen with the element, like it not existing. As such, the error description can be highly deceptive. It should have probably read:

Failure to bind bindings to element. Possible reasons include: multiple binding attempts, element not existing, element not in DOM hierarchy, quirks in browsers, etc

Upvotes: 1

Two Seeds
Two Seeds

Reputation: 53

I was getting the same error in IE 7/8. Worked fine in all other browsers including IE 9/10.

What I found worked for me was eliminating self closing tags.

Bad:

<div>
    <span data-bind="text: name"/>
</div>

Good:

<div>
    <span data-bind="text: name"></span>
</div>

Upvotes: 1

Jason Parker
Jason Parker

Reputation: 4994

I had this error occur for a different reason.

I created a template for save/cancel buttons that I wanted to appear at top and bottom of the page. It worked at first when I had my template defined inside of a <script type="text/html"> element.... but then I heard you could optionally create a template out of an ordinary <div> element instead.

(This worked better for me since I was using ASP.NET MVC and none of my @variableName Razor syntax was being executed at runtime from inside of the <script> element. So by changing to a <div> instead, I could still have the MVC Razor engine generate HTML inside my Knockout.js template when the page loads.)

After I changed my template to use a <div> instead of a <script> element, my code looked like this.... which worked fine on IE 10. However, later when I tested it on IE 8, it threw the error:

You cannot apply bindings multiple times to the same element

HTML:

<div id="mainKnockoutDiv" class="measurementsDivContents hourlyMeasurements">

  <div id="saveButtons_template" style="display: none;">
    ... my template content here ...
  </div>

  <!--ko template: { name: 'saveButtons_template' } -->
  <!--/ko-->

  Some_HTML_content_here....

  <!--ko template: { name: 'saveButtons_template' } -->
  <!--/ko-->

</div>

JavaScript:

ko.applyBindings(viewModel, document.getElementById('mainKnockoutDiv'));

THE SOLUTION:

All I had to do was move my saveButtons_template <div> down to the bottom, so that it was outside of the mainKnockoutDiv. This solved the problem for me.

I suppose Knockout.js was trying to bind my template <div> multiple times since it was located inside the applyBindings target area... and was not using the <script> element.... and was being referenced as a template.

Upvotes: 1

Blaise
Blaise

Reputation: 22242

Updated Answer

Now that we can use dataFor() to check if the binding has been applied, I would prefer check the data binding, rather than cleanNode() and applyBindings().

Like this:

var koNode = document.getElementById('formEdit');
var hasDataBinding = !!ko.dataFor(koNode);
console.log('has data binding', hasDataBinding);
if (!hasDataBinding) { ko.applyBindings(vm, koNode);}

Original Answer.

A lot of answers already!

First, let's say it is fairly common that we need to do the binding multiple times in a page. Say, I have a form inside the Bootstrap modal, which will be loaded again and again. Many of the form input have two-way binding.

I usually take the easy route: clearing the binding every time before the the binding.

var koNode = document.getElementById('formEdit');
ko.cleanNode(koNode);
ko.applyBindings(vm, koNode);

Just make sure here koNode is required, for, ko.cleanNode() requires a node element, even though we can omit it in ko.applyBinding(vm).

Upvotes: 2

Jess
Jess

Reputation: 25137

If you are reusing an element over and over (a Bootstrap modal dialog in my case), then calling ko.applyBindings(el) multiple times will cause this problem.

Instead just do it once like this:

if (!applied) {
    ko.applyBindings(el);
    applied = true;
}

Or like this:

var apply = function (viewModel, containerElement) {
    ko.applyBindings(viewModel, containerElement);
    apply = function() {}; // only allow this function to be called once.
}

PS: This might happen more often to you if you use the mapping plugin and convert your JSON data to observables.

Upvotes: 5

Martin Lottering
Martin Lottering

Reputation: 1812

I finally solved mine by returning { controlsDescendantBindings: true } in the init function of the binding handler. See this.

Upvotes: 5

Pete
Pete

Reputation: 2463

There are a lot of great answers for this issue but I have a newbie answer.

I found that I accidentally added the same script in two places and it was trying to bind twice. So before you pull your hair out, make sure you check for this issue.

Upvotes: 8

Almer
Almer

Reputation: 1199

Something that can happen as well which throws this exception is the following. Say you have:

ko.applyBindings(myViewModel1, document.getElementById('element1'));
...
ko.applyBindings(myViewModel2, document.getElementById('element2'));

Now, when both #element1 and #element2 don't exist you'll get the error. The reason is that Knockout's applyBindings falls back on document.body as root element when #element1 and #element2 are not found. Now it tries to apply binding twice on the body...

Not a nice fallback of Knockout if you ask me. I'd rather have a clear error message that the element does not exist in the DOM (yet).

Upvotes: 32

vprasad
vprasad

Reputation: 1461

You need to remove the bindings before you use applyBindings again:

ko.cleanNode($element[0]);

Upvotes: 102

x0n
x0n

Reputation: 52480

You should never apply bindings more than once to a view. In 2.2, the behaviour was undefined, but still unsupported. In 2.3, it now correctly shows an error. When using knockout, the goal is to apply bindings once to your view(s) on the page, then use changes to observables on your viewmodel to change the appearance and behaviour of your view(s) on your page.

Upvotes: 13

Mitja Gustin
Mitja Gustin

Reputation: 1791

Two things are important for above solutions to work:

  1. When applying bindings, you need to specify scope (element) !!

  2. When clearing bindings, you must specify exactly same element used for scope.

Code is below

Markup

<div id="elt1" data-bind="with: data">
    <input type="text" data-bind="value: text1" >
</form>

Binding view

var myViewModel = {
  "data" : {
    "text1" : "bla bla"
  }
}:

Javascript

ko.applyBindings(myViewModel, document.getElementById('elt1'));

Clear bindings

ko.cleanNode(document.getElementById('elt1'));

Upvotes: 13

Related Questions