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
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>
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.
Related
I want to access C# List in my view but in Javascript code as i am using Jquery plugin and this list is basically a dataset for that.
JavaScriptSerializer jSearializer =new JavaScriptSerializer();
vModel.JSONResultVMToDoList = jSearializer.Serialize(vModel.VModelToDoList);
this is what returning from model to my view
If I understand what you are trying to do correctly, you are going about it the wrong way. You need to pass the original vModel.VModelToDoList to your strongly-typed view and then iterate it using C# (Razor), to add rows to a table or whatever. Forget serializing it as JSON. Your code seems to be passing the data to the view twice - once as a .NET data structure and then again as JSON.
The code in the cshtml would be something like
<table>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Property1)</td>
<td>#Html.DisplayFor(modelItem => item.Property2)</td>
<td>#Html.DisplayFor(modelItem => item.Property3)</td>
</tr>
}
</table>
Any object passed from the controller to a view as the model only exists on the server, it never gets to the client browser to be available for use by client-side JavaScript.
(Incidentally, a completely different approach would be to write a WebAPI returning JSON and then call this from JavaScript on the browser. This might be better if you want to provide fast and frequent updates, but if the data is fairly static for a time just create it client-side as mentioned above.)
(Sorry for the delay in responding!)
I am not sure that is the best way of presenting data to your users but if you have to do this, stick the JSON in a hidden field in the view with a suitable ID. Then in the document ready handler, retrieve the string, parse it to an array, iterate the array and show alerts.
I have a MVC 4 validation & Knockout issue.
I tried this solution :
Translate knockout into razor to keep it's validation working
But i have the same problem as listed in comment of the valid answer => the validation works only for the first element.
After several searches, i found this article on model binding to a list :
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
I am wondering how to merge those two solutions.
Sample with validation working only for the first element :
Dim dummy As ViewModels.ObjectViewModel = Model.MyObjects.FirstOrDefault
#<tbody data-bind='foreach: MyObjects'>
<td>
#Html.TextBoxFor(Function(model) dummy.Label, New With {.data_bind = "value: Label"})
</td>
</tbody>
Sample with validation working, but without the knockout foreach, i can't add items dynamically
<tbody>
#Code
Dim i As Integer = 0
For Each object In Model.MyObjects
#<tr>
<td>
#Html.TextBoxFor(Function(m) Model.MyObjects(i).Label)
</td>
</tr>
i+= 1
Next
End Code
</tbody>
Maybe something with knockout variable : $index() ?
In the past I’ve tried to force combining razor and knockout. But more recently I just opt to go one way or the other. If I’m going to render something on the client side, then I’ll just go ahead and define everything in terms of HTML directly instead of going through razor.
Probably your best bet here is to just define the HTML elements directly. If you need to have validation on place, then just make sure of two things:
Set the corresponding jquery validate attributes (e.g. data-val-true) so that the form validates on the client side.
If you’re submitting data to an ASP.NET MVC Controller make sure the elements have the same name/id as needed by the controller so that the binding takes place on the controller method parameters.
So, after many searches and tests, i have found the solution :
1° Step is to put a correct name for the validation.
Razor View Code :
Dim dummy As ViewModels.ObjectViewModel = Model.MyObjects.FirstOrDefault
<tbody data-bind='foreach: MyObjects'>
<td>
#Html.TextBoxFor(Function(model) dummy.Label, New With {.data_bind = "value: Label, attr: { name: 'MyObjects[' + $index() + '].Label'}"})
</td>
</tbody>
You get this HTML for the first item : name="MyObjects[0].Label"
Which is cool and makes validation work.
But if you add an item dynamically, the validation won't work for the new item.
2° Step is to make the unobstrusive validation re parse your form.
JavaScript
viewModel.addObject = function () {
viewModel.MyObjects.push(new object())
$("form").data("validator", null);
$.validator.unobtrusive.parse($("form"));
};
Those two answers helped me a lot :
Knockout MVC with existing C# code - looping
MVC Model Validation on a dynamic form?
So I have two separate models, one 'items' model, the second a 'sites'
model... I'm using KO to bind this data to two separate elements on
the DOM (and working as needed), but I've found a need to replace one
of my bound columns on one of my models, with data from the other.
On my 'items' model, I have a site ID column, that I'd love to swap
with the actual 'SiteName' property on that model (simple name, value-
pair - SiteName, SiteId)... Does any one know a way to do this within
KO?
I really want to keep the model data itself in tact on the server side,
verses just creating a custom model on the server side that does it for me.
I'm sure I could give those elements a special class, and loop
through them and replace them manually with jQuery, but I thought that
KO might have an easier way of doing this.
Thanks!
I have tried something like this, but it doesn't seem to work (yes, I know div tags within a table element are not standards based, I just wanted to see if it would work, and if it did, I'd transition from the table to another formatting option)
<tbody data-bind="foreach: items">
<tr data-bind="click: updateItem">
<td data-bind="text: ItemName"></td>
<div data-bind="foreach: sites">
<div data-bind="if: items.SiteId = sites.SiteId">
<td data-bind="text: sites.SiteName"></td>
</div>
</div>
The jQuery to do this using my returned model is:
$(function () {
$('#allItems tr .siteIdCell').each(function () {
for (i in allSites) {
if (allSites[i].SiteId == $(this).html()) {
$(this).html(allSites[i].SiteName);
}
}
});
});
Where .siteIdCell is the class I applied to the columns using this value, and allSites is the object array I'm receiving via JSON.
Not sure if I'll get many responses, but I just figured I'd update this if anyone else has the same issue, and there's no ability to do this in KO.
The situation is as follows:
I have a ViewModel that has a property that's a List<Product>, where Product is a class with let's say properties Property1, Property2 and Property3.
I have to render the ViewModel and I wish to render the List<Product> in an HTML table each row of which has a "Delete" button for that row.
Underneath the afore-mentioned HTML table, there should be 2 buttons:
3.1 "Add" - used to add a new empty Product to the list
3.2 "Use default product list" - the List has to be loaded via an AJAX call to the GetDefaultProduct() action of the controller
Clicking on a "Delete", "Add" or "Use default product list" button should not post the entire page
The model contains some other lists of items as well - for the sake of example: List<Sales>, List<Orders> and so on. I'm hoping I can re-use the solution for the List for those lists as well.
Is there a way to do this with ASP.NET MVC?
If yes, what is the best way to do it with ASP.NET MVC?
I did this with jQuery templating and I managed to implement this with simple operations, but I have to do it in an ASP MVC solution and I'm still trying to get the hang of the technology.
I've been reading about the Editor template, RenderAction, Async action and partial views and I'm trying to compose a solution with them and I'll post it if it works.
Thanks in advance for any suggestions and comments!
UPDATE
The solution lies (as Darin pointed out) in Steve Sanderson's blog post.
However, it assumes that the reader is aware of under-the-covers way of how a List of objects should be rendered in a CRUD-friendly, indexed manner.
So, in order to help anyone who wants to have an indexed list of omplex objects, I suggest reading mr. Haacked's blog post: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx.
After you are done with it, you can move on to Sanderson's blog. By the way, take a good look at the BeginCollectionItem custom HTML helper - its implementation isn't a trivial one as one might think.
The demo project is a sight for sore eyes - it's spot on and easy to understand.
The proposed solution DOES use some jQuery.ajax() calls (for the Add link), but just out of bare necessity.
PS: It's a bit frustrating that one has to read an explicit article from one of the developers of ASP.NET in order to find out that there's an implicit CoC (Convention-over-Configuration) in the default model binder - it just knows how to work with Lists, but no out-of-the-box HTML helper (or anything similar) doesn't let you in on this.
Personally I think that CRUD-friendly rendering of List<object> is a very common scenario, not an edge case so it should be simpler to use and a part of the ASP.NET MVC out-of-the-box machinery.
I would recommend you reading the following blog post. It would definitely put you on the right track for implementing an editing scenario of a variable length list.
This is a very easy way of doing it if you want to do using Jquery.
ASP.Net MVC 3 JQGrid
Save yourself some pain and download the Telerik Extensions for ASP.Net MVC. It's open source ( though there's a paid option).
The Grid control (possibly in conjunction with the Window control) will give you all the UI functionality you need and there's plenty of online examples.
I've been doing a lot of master-detail UI work recently and Telerik's been an immense help.
Maybe this is not the answer you are looking for, but hey, try to create a default typed edit view for a model using folowing settings:
this will generate the folowing code for view (assuming Razor engine):
#model IEnumerable<MvcApplication2.Models.User>
#{
View.Title = "GetData";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>GetData</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th></th>
<th>
Name
</th>
<th>
Description
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
#Html.ActionLink("Details", "Details", new { id=item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
<td>
#item.Name
</td>
<td>
#item.Description
</td>
</tr>
}
</table>
The code wich will handle edit/update/details will accept Id of the entity as a parameter. These are paricualr other methods of your controller which will handle editting/adding/previewing of you item
I have lots of complex HTML reports in my current project where there we perform lots of conditional rendering of TRs and TDs with rowspans and colspans.
It could sometimes look like this (this is extremely simplified):
<tr>
#foreach (var ourItem in ourList) {
if (ourItem != ourList.First()) {
<tr>
}
<td></td>
</tr>
}
However, Razor claims: "The foreach loop is missing a closing "}" character". (within Visual Studio)
I've tried to wrap the <tr> in <text></text> which makes the closing } problem go away only to find this when run: "Encountered end tag "tr" with no matching start tag. Are your start/end tags properly balanced".
How would I do this kind of conditional rendering while convincing Razor not to bother about the HTML at all, cause the HTML is balanced when all the looping is done. Or at least that was the case when the ASP.NET View Engine was used.
Visual Studio Intellisense and syntax highlighting is not one of the best but in this case it warns you that if the condition is not satisfied, you might get invalid markup and you shouldn't blame it for this.
The important thing is that your project runs fine but you might consider externalizing this logic into HTML helpers because if what you are saying is true that this is a simplified version of what you have in the views I don't even want to imagine how your actual code looks.
IMHO having so much conditional logic in a view is an abuse. You definitely should consider using HTML helpers or controls such as MVCContrib Grid.
UPDATE:
You may try the following hack:
<tr>
#foreach (var ourItem in ourList) {
if (ourItem != ourList.First()) {
#:<tr>
}
#:<td></td>
#:</tr>
}
Razor depends on matching tags to determine the automatic transitions between code and markup. You cannot "disable" this feature of Razor (at least not without rewriting large parts of the Razor parser).
You can work around it by using Darin's suggestion, though I don't understand (at least not from your simplified example) why your view needs to be so convoluted. Why not write the following code instead:
#foreach (var ourItem in ourList) {
<tr>
<td>...</td>
</tr>
}
While tags might balance out in the generated markup, the source that you provided makes it very difficult to reason about its correctness.
When trying to use rowspan and trying to get a structure like
<table>
<tr>
<td rowspan=2>1:st col</td>
<td>2:nd col</td>
</tr>
<tr>
<td>2:nd col</td>
</tr>
</table>
You could try:
#{
var ourList = new List<string> { "1", "2", "3" };
}
<table border=1>
#foreach(var ourItem in ourList){
<tr>
#if (ourItem == ourList.First())
{
<td rowspan="#ourList.Count()">#ourItem</td>
}
<td>#ourItem</td>
</tr>
}
</table>
I had a similar problem - my solution Html.Raw("");
if (isTrue)
{
<text><div></text> }
...
if(isTrue) {
#Html.Raw("</div>"); // <-- closing tag!
}