KnockoutJS with SharePoint: Fixing ‘Error: You cannot apply bindings multiple times to the same element.’
I’ve been using KnockoutJS to build functionality into SharePoint pages for a while now. (In case you’ve ever wondered, yes, it’s the same thing people mean when they simply say “Knockout” or “KO”.)
Setting everything up with KnockoutJS can take a bit longer than hard-wiring things with jQuery – depending on what you are trying to accomplish and what your background may be – but once it’s all in place, changes to functionality become much easier. Some people naturally think in the Model-View-View Model (MVVM) pattern, but it took some time for me to get my head around the idea. Early on it felt like not just a separation of church and state, but also separation of the apse from the nave and Arkansas from middle, lower Arkansas.
I’ve probably run into every newbie error in the book, and this one has gotten me multiple times.
The screen snippet above is from my favorite debugger, Firebug. The error message itself doesn’t give you much to go on. It makes basic sense, since multiple bindings on the same element would seem to be a bad idea, but it doesn’t tell you anything about why it’s happening or what you’ve done to trigger the error. It just kills the page load.
Let’s take a simple example. In one of the sets of pages I’m working on, we want to have a header section driven by one view model and a body section driven by another. The two bindings are in separate .js files because the header logic is reused across pages, while the body section is unique to individual pages. (We’re using SharePoint 2010 more as a back-end repository and identity management provider in this application and replacing virtually all the UI with custom pages.) Each of the .js files performs the binding for which it is responsible.
The markup – at a high level – looks something like this:
<div id="abc-page-header"> ... </div> <div id="abc-page-body"> ... </div>
and the bindings occur in the JavaScript looking something like this:
ko.applyBindings(new HeaderViewModel(), document.getElementById("abc-page-header")); ... ko.applyBindings(new BodyViewModel(), document.getElementById("abc-page-body"));
As you can see, each binding is scoped to the container for which it has responsibility. Using document.getElementById
is straighforward: we should get a JavaScript object reference to the containing element with that id.
All well and good, right? The problem in this case comes in when you have no second argument, or even worse, a typo in the id. For instance, I ran myself silly with document.getElementById("ab-page-body")
. Because the document.getElementById
function returned null (the id wasn’t in the page), the binding occurred on the entire document, and therein arises the problem. Elements with data-bind now become bound twice, throwing the error.
It would be much better if the error told us a little more about what was happening, of course. For instance, the error might mention which binding is currently applied to the particular element where the issue arises. Even better would be some sort of warning when you attempt to apply bindings with a null parameter. Alas, this is not the case.
The simple fix is to find the place in your code where a ko.applyBindings
is applied incorrectly. It’s not so hard if you know that’s the right thing to do. If you don’t, you may end up tearing down your code and starting afresh, which is rarely productive.
See Creating view models with observables for the [rather sparse] documentation on ko.applyBindings
. Note that the second parameter must be a JavaScript DOM node, not a jQuery object. If you try to get fancy like this:
ko.applyBindings(new HeaderViewModel(), $("#abc-page-header"));
You’ll get a very clear error:
Moral of the story: always scope your bindings carefully!
This is probably just the way I taught myself to use knockout but rather than using multiple bindings on my page for separate view models, I use a parent container view model which holds my models/viewmodels in that container. One of the biggest benefits is passing data across from one view model to another.
Here is a simple explanation:
http://stackoverflow.com/questions/8676988/example-of-knockoutjs-pattern-for-multi-view-applications/8680668#8680668
Bryan:
Thanks for the link. There’s always more than one way to skin a cat, eh?
M.
Can fix:
ko.applyBindings(new HeaderViewModel(), $(“#abc-page-header”));
With:
ko.applyBindings(new HeaderViewModel(), $(“#abc-page-header”)[0]);
ko.applyBindings(new HeaderViewModel(), $(“#abc-page-header”).get(0));