0

I'm trying to convert my MVC app's _Layout page to use a knockout viewmodel instead of Razor syntax. So far, all of my content pages have syntax like the following to render javascript ViewModels (for Index view):

<script type="text/javascript">
$(document).ready(ko.applyBindings(new IndexVm(
    @Html.Raw(JsonConvert.SerializeObject(Model, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() })))));
</script>

This has been working great so far. Now, in my Layout, I tried to use the same approach with the following:

<script type="text/javascript">
$(document).ready(ko.applyBindings(new LayoutVm(
    @Html.Raw(JsonConvert.SerializeObject(ViewBag, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() })))));
</script>

If the content page doesn't have an inner viewmodel declaration, this works. But when I load up the Index page (with the first snippet), I get the following:

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

I'm a bit stumped as to why this isn't working. Any help would be much appreciated!

RobVious
  • 12,685
  • 25
  • 99
  • 181
  • One workaround might be to declare your inner viewmodel's observables and observableArrays in your _Layout page's viewmodel. Then on your content page, just fill those observables with data. Of course, that depends on the scope of your project, the details of the data you are using, etc – rwisch45 Dec 18 '13 at 03:00
  • possible duplicate of [KnockoutJS: ko.applyBindings to partial view?](http://stackoverflow.com/questions/7342814/knockoutjs-ko-applybindings-to-partial-view) – Alerty Dec 18 '13 at 04:10

3 Answers3

2

You must understand that ASP.NET will accomplish certain tasks on the server before sending an HTML document to the browser. It will interpret the Razor statements as well as assemble the partial views, views and layout together into a single HTML document on the server.

Knockout is framework that works on the client (browser). If you apply bindings in your _Layout.cshtml and your Index.cshtml, you be applying bindings twice in the assembled HTML document. In Knockout, you can't apply bindings multiple times on the same HTML elements.

What you will need to do is add an id attribute with a meaningful value to certain HTML elements. Then, you will need to add a second parameter to your different ko.applyBindings which will be the element id.

Now you can always control the descending bindings yourself if you have a case where there are nested elements. You can specify a statement that will stop the binding of a parent element from going down the child elements. Find out more about this on http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html.

Alerty
  • 5,945
  • 7
  • 38
  • 62
1

Well you can do this by simply providing placeholders in html:

<div id="index">
   <!-- index page goes here -->
</div>

and

<div id="layout">
   <!-- layout page goes here -->
</div>

Then you can apply view model as follows:

<script type="text/javascript">
$(document).ready(ko.applyBindings(new IndexVm(
@Html.Raw(JsonConvert.SerializeObject(Model, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() }))), document.getElementById('index')));
</script>

and

<script type="text/javascript">
$(document).ready(ko.applyBindings(new LayoutVm(
@Html.Raw(JsonConvert.SerializeObject(ViewBag, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() }))), document.getElementById('layout')));
</script>
0

ko.applyBindings can take two parameters, the first being your viewmodel, and the second being an optional root node for the binding context to be set. if no value is supplied, the root node default is window.document.body. if you call applyBindings twice without specifying different root nodes, then it will give you the error you are receiving.

keep in mind, that you do not want to overlap nodes that are being bound. if you need to call applyBindings twice for two separate viewmodels, you must specify different elements to bind to:

ko.applyBindings(new MenuVM(), document.getElementById('menu'));
ko.applyBindings(new ContentVM(), document.getElementById('sub-content'));

edit based on rwisch45's comment, an option is to have a single viewmodel be bound to the entire page, and set child viewmodels inside the main viewmodel.

http://jsfiddle.net/TNR89/

Kevin Nacios
  • 2,843
  • 19
  • 31