1

Where the server is sending the client a complex object and the goal is to transition from C#'s 'foreach' to KnockoutJS's 'data-bind="foreach: ' consider this code that populates a shopping cart with various pieces of info:

@{
    foreach (var item in GetItems(Model))
    {
    <dt>
        <input type="radio" id='mode_@(item.ID)' name="mode" value="@item.ID" />
        @item.Label - $@item.PriceToAdd
    </dt>
    <dd>
        @Html.Raw(item.Explanation) </dd>
        }
    }
}

Should the server's code be adjusted to flatten out the object before rendering the View or can KnockoutJS deal with unwrapping it? Would it get easier if the server sends JSON?

FOLLOWING UP: It becomes clear the question boils down to mapping plugin and mfanto's first answer gets me part the way there:

self.items = ko.mapping.fromJS(@Html.Raw(JsonConvert.SerializeObject(Model.Items)));

firebug shows me output of:

self.items = ko.mapping.fromJS([{"ID":60},{"ID":62},{"ID":63},{"ID":64},{"ID":9}]);

Perhaps mapper fails because one of my Items (id=9) has different elements than the rest.

Probably I need to research one of the more advances usages of mapper?

FORMATTED OUTPUT COMPARES VALUES RETURNED BY JsonConvert vs. JavaScriptSerializer

...
self.itemsJSON = ko.mapping.fromJS(@Html.Raw(JsonConvert.SerializeObject(Model.Items)));
self.items = @Html.Raw(new JavaScriptSerializer().Serialize(Model.Items));

when the above code renders to a breakpoint in Firebug:

self.itemsJSON = ko.mapping.fromJS([{"ID":60},{"ID":62},{"ID":63},{"ID":64},{"ID":9}]);
self.items = [  //line breaks inserted for clarity
{"Explanation":"Item1's text.","Label":"Item1's Label","MsgConfirm":null,"PriceToAdd":1255,"TaxExempt":false,"PercentToAdd":0,"SortOrder":1,"ID":60},
{"Explanation":"Item2's text.","Label":"Item2's Label","MsgConfirm":null,"PriceToAdd":1255,"TaxExempt":false,"PercentToAdd":0,"SortOrder":2,"ID":62},
{"Explanation":"Item3's text.","Label":"Item3's Label","MsgConfirm":null,"PriceToAdd":295,"TaxExempt":false,"PercentToAdd":0,"SortOrder":3,"ID":63},
{"Explanation":"Item4's text.","Label":"Item4's Label","MsgConfirm":null,"PriceToAdd":395,"TaxExempt":false,"PercentToAdd":0,"SortOrder":4,"ID":64},
{"Explanation":null,"Label":"[foo]","MsgConfirm":null,"PriceToAdd":150,"TaxExempt":false,"PercentToAdd":0,"SortOrder":99,"ID":9}
];

thx

justSteve
  • 5,444
  • 19
  • 72
  • 137
  • IMO, the best answer to this question is for you to read (and understand) 32bitkid's excellent response to this SO question which is somewhat similar to yours: http://stackoverflow.com/questions/8894442/mvc-razor-view-nested-foreachs-model – brettbaggott Nov 29 '12 at 22:02
  • I'd be happy to take that seminar if my initial take lead me to think it had anything to do with KnockoutJS. My Razor code works fine - i'm trying re replicate it in Knockout. – justSteve Nov 29 '12 at 22:31
  • By returning only the ID values (as noted for the Firebug output above) instead of the complex 'multi-element' object. – justSteve Nov 30 '12 at 18:55
  • Can you post the objects you're serializing? If the output is wrong above, then it's not ko.mapping causing it, but the javascript serializer. The {"ID" : 60} is the result of Html.Raw(JsonConvert.SerializeObject(...)) – mfanto Nov 30 '12 at 19:07
  • It's a little odd that JSON.NET isn't serializing things properly, but it looks like it works now? self.items = @Html.Raw(new JavaScriptSerializer().Serialize(Model.Items)); and then inside the for loop, – mfanto Nov 30 '12 at 21:13

2 Answers2

1

You don't need to flatten the object before you use Knockout. The ko.mapping plugin will create viewmodels with observable properties, and can handle complex nested objects.

To use it with an ASP.NET MVC model, use @Html.Raw() and a Json serializer (in this case Json.NET:

function AppViewModel() {
    var self = this;
    self.items = ko.mapping.fromJS(@Html.Raw(JsonConvert.SerializeObject(Model.Items)));
}

ko.applyBindings(new AppViewModel());

From there, you can use foreach:

<table>
    <tbody data-bind="foreach: items">
        <tr>
            <td data-bind="text: PriceToAdd()"></td>
        </tr>
    </tbody>
</table>
mfanto
  • 14,168
  • 6
  • 51
  • 61
0

You can go either way with this. Render it on the server with Razor or render it on the client with knockout... The more fundamental question is where do you want to render it. There is no right or wrong answer here.

If you go with knockout, you need to deal with more than just having the server possibly flatten out your model. Knockout will require ajax requests to both read and then save your data and this is where the two solutions fundamentally differ I don't see any JavaScript as part of your solution and without that component, providing a ko solution is pretty impossible.

If you are thinking about using knockout simply as a client side templating engine, then something like jsrender is likely a better solution.

andleer
  • 22,388
  • 8
  • 62
  • 82
  • Thanks for the bigger picture concern - while I should have known enough to focus squarely on KO in the first place, your link to rsRender makes sense if not for the 2-way traffic I'll need to support. – justSteve Nov 30 '12 at 18:38