Razor View Engine: Complex looping and HTML - asp.net-mvc

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!
}

Related

Asp.Net MVC validation & knockout foreach

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?

Refactor a C# loop to Knockout Mapping Plugin

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.

How can a list of objects (a part of a bigger Model) with with Add and Delete buttons in MVC be updated with AJAX calls?

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

Passing html markup into an ASP.NET User Control

In the following example,
<uc1:MyUserControl>
<p>this is html</p>
</uc1:MyUserControl>
How do I access "<p>this is html</p>" as a string within MyUserControl so that I might inject it into the response?
I'm not talking about passing in a string parameter like <uc1:MyUserControl myparameter="<p>this is html</p>" />, but how do I access true multi-lined intellisensed HTML markup either between the opening and closing tags or by some other mechanism such as a <MessageTemplate> tag.
Bonus points for a solution that works in ASP.NET MVC 3!
EDIT:
Thanks to StriplingWarrior and this link as the missing puzzle piece, magic was made:
So, in any view:
<%# Register src="../../Shared/Ribbon.ascx" tagname="Ribbon" tagprefix="uc1" %>
...
<uc1:Ribbon ID="Ribbon" runat="server">
<Content>
Hello world! I am <b>pure html</b> being passed into a UserControl!
</Content>
</uc1:Ribbon>
In Ribbon.ascx:
<%# Control Language="C#" CodeBehind="Ribbon.ascx.cs" Inherits="NunYourBeezwax.Views.Shared.Ribbon" %>
<table>
<tr>
<td>I am reusable stuff that wraps around dynamic content</td>
<td><%= this.Content %></td>
<td>And I am stuff too</td>
</tr>
</table>
And finally, in Ribbon.ascx.cs (Need to manually add in MVC)
using System.Web.Mvc;
using System.Web.UI;
namespace NunYourBeezwax.Views.Shared
{
[ParseChildren(true, "Content")]
public class Ribbon : ViewUserControl
{
[PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
public string Content { get; set; }
}
}
Will Render as:
<table>
<tr>
<td>I am reusable stuff that wraps around dynamic content</td>
<td>Hello world! I am <p>pure html<p> being passed into a UserControl!</td>
<td>And I am stuff too</td>
</tr>
</table>
In typical WebForms controls, the HTML will be automatically put into a Literal control in MyUserControl's Controls collection. Controls with template properties work a little differently, but you may still be able to access this in a similar way through the property whose name you're using (e.g. MessageTemplate).
MVC works totally differently, and I don't know if there's a way to do what you're asking there. You may want to consider using javascript to analyze what actually gets rendered client-side if it doesn't work for you.

Razor syntax inside attributes of html elements (ASP MVC 3)

I have a table with repeating customer rows, I would like to add the customer ID to the ID attribute of my table rows like this:
<tr id="row<customer id>"></tr>
I try adding this code:
#foreach(var c in Model) {
<tr id="row#c.id"></tr>
}
Which gives me the following output:
<tr id="row#c.id"></tr>
<tr id="row#c.id"></tr>
etc.
But I would like it to be:
<tr id="row1"></tr>
<tr id="row2"></tr>
etc.
I also tried to add <tr>row#{c.id}</tr> but it did not work..
have you tried <tr>row#(c.id)</tr>?
The actual reason why this doesn't work is because your row#c.id matches the regex for an email address. So the parser assumes it's an email and not actually an attempt to call code. The reason row#{c.id} doesn't work is because the #{} doesn't output and is meant to contain blocks of code.
When in doubt you should use #() as it will force what's contained between the () to be parsed as code.

Resources