Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout multiple bindings on nested dom elements

I've managed to get myself into a bit of trouble with a project I'm working on.

Originally the site has one page on it that uses Knockout, with the other pages using jQuery. Due to some problems with the Foundation modal placing itself in the root of the body element, I ended up applying the bindings for the viewmodel for this page to the body element.

Fast forward 4 months, and without foreseeing the trouble I'm in now, I went and rebuilt our shopping basket in Knockout. The shopping basket is visible on every page and is included using a ZF2 partial.

Going back to the page I worked on 4 months ago, it is completely broken with the error message in console saying:

Uncaught Error: You cannot apply bindings multiple times to the same element.

Here's some code to show my layout:

<html>
    <head>
        <title>My Website</title>
    </head>
    <body> // 4 month old SPA bound here
        <nav>
            <div id='shopping-basket'> // Shopping basket bound here
                ...
            </div>
        </nav>
        <div id='my-app'>
           ...
        </div>
    </body>
</html>

JavaScript:

var MyAppViewModel = function() {
   // logic
};

var ShoppingBasketViewModel = function() {
    //logic
};

ko.applyBindings(new MyAppViewModel(), document.body);
ko.applyBindings(new ShoppingBasketViewModel(), document.getElementById('shopping-basket');

If I had the time I could go back and rework the original application to sit within it's own div container that would site side by side with the basket, but unfortunately this isn't an option.

The other option is to discard the last bit of work I did on the shopping basket and replace it with jQuery, but this would mean losing a weeks worth of work.

Is there anyway when I'm applying the bindings that I could have both viewmodels working side by side, while being nested in the Dom, and remaining independent of each other?

like image 757
Tom Avatar asked Jun 22 '15 09:06

Tom


1 Answers

I had some similar problem. I needed to applybinding to specific nested elements and start with a bind on the document first. Same problem. My solution was to add some ignore element part and than bind the specific element manually.

1) Add a custom binding so you can skip binding on the specific shopping basket:

ko.bindingHandlers.stopBinding = {
    init: function() {
         return { controlsDescendantBindings: true };
    }
};
ko.virtualElements.allowedBindings.stopBinding = true;

2) Add the custom binding in your html (surround your shopping basket):

<html>
<head>
    <title>My Website</title>
</head>
<body> // 4 month old SPA bound here
    <nav>
        <!--  ko stopBinding: true -->
        <div id='shopping-basket'> // Shopping basket bound here
            ...
        </div>
        <!--  /ko -->
    </nav>
    <div id='my-app'>
       ...
    </div>
</body>

3) Apply your bindings as you already do:

ko.applyBindings(new MyAppViewModel(), document.body);
ko.applyBindings(new ShoppingBasketViewModel(), document.getElementById('shopping-basket');

4) The first bind will skip the shopping-basket because of your custom binding handler and your second bind will explicitly bind the shopping-basket.

I haven't tested the code above on your specific example, but it should point you into the correct direction.

like image 114
gkempkens Avatar answered Sep 22 '22 18:09

gkempkens