Selecting an alternate EditorFor template for a List - model-view-controller

I have an object that represents a food item to order at a restaurant. This object has a list of Modifier Groups (sides, cooking instructions, pizza toppings, whatever) and each list has a list of Modifiers.
Certain Modifier options need to be displayed differently (for example, toppings need to specify left/right/all), even though they are the same data type.
I am trying use #Html.EditorFor and specify the alternate EditorTemplate when required.
In /Views/Shared/EditorTemplates I have ModifierSelection.cshtml and ToppingSelection.cshtml. I am calling them in the following view:
#model MyApp.ViewModels.ModifierSelectionList
<div class="menugroup">
<h3 class="menuname">#Model.ModifierListName: (Select #Model.MaximumSelections)</h3>
<div class="modcountvalidation">#Model.ValidationResults</div>
#Html.HiddenFor(model => Model.ModifierListId)
<table class="menu">
#if (Model.IsToppingsList)
{
#Html.EditorFor(model => Model.ModifierSelections, "ToppingSelection")
}
else
{
#Html.EditorFor(model => Model.ModifierSelections)
}
</table>
</div>
When I try to display an item that requires the "ToppingSelection" EditorTemplate instead of the default, I get the following error:
System.InvalidOperationException was unhandled by user code
Message=The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[MyApp.ViewModels.ModifierSelection]', but this dictionary requires a model item of type 'MyApp.ViewModels.ModifierSelection'.
Source=System.Web.Mvc
So - I have a set of EditorTemplates for a data type. I am trying to use them to display a list of items and I need to be able to select which one to use.
What am I doing wrong?
Thanks!

OK, here is the real solution. Rather than iterating through the list using foreach, I had to iterate using a for loop.
#for (int i = 0; i < Model.ModifierSelections.Count; i++ )
{
if (Model.IsToppingsList)
{
#Html.EditorFor(m => Model.ModifierSelections[i], "ToppingSelection")
}
else
{
#Html.EditorFor(m => Model.ModifierSelections[i])
}
}

Solved!
Apparently, if you send a list type to Html.EditorFor and do not specify a template, it will iterate through the list and display each item using the template that it finds for the item type. If you do specify a template, it will not iterate through the list and send each item to that template, it will attempt to send the entire list to your template, which isn't the right data type.
I fixed it by manually iterating through the list:
#foreach (var modifierSelection in Model.ModifierSelections)
{
if (Model.IsToppingsList)
{
#Html.EditorFor(m => modifierSelection, "ToppingSelection")
}
else
{
#Html.EditorFor(m => modifierSelection)
}
}

Related

How to alternate row style without javascript for DisplayFor/EditorFor bound to a list of model

How can I pass the index of the current element to the View for DisplayFor/EditorFor so that I can decide if the row should show the alternate style or not?
My main view looks like this:
<table>
#Html.EditorFor(model => model.MyListOfItems)
</table>
The view used for the EditorFor looks like:
#Html.HiddenFor(model => model.IdOfTheItem)
<tr class="shouldBeChangedDependingOnRowEvenOrNot">
<td>#Html.CheckBoxFor(model => model.MarkForBatchEdit)</td>
<td>#Html.DisplayFor(model => model.NameOfThisItem)</td>
<td>#Html.DisplayFor(model => model.StateOfThisItem)</td>
</tr>
Well I am aware of this similar question and the suggested solution: alternating row color MVC
But I can't apply them to this case.
Any suggestions?
The EditorFor is overloaded to take an additionalViewData parameter, so you could pass the index in the ViewData, which is a collection of key/value pairs.
#Html.EditorFor( model => model.MyListOfItems , new { CurrentIndex = SomeNumber } )
In your view you would get the value using ViewData["CurrentIndex"].
Also, instead of passing the element index, why not do the calculation in your controller and pass whether you have an even or odd row in your ViewData.
bool isEvenRow = ((CurrentElementIndex % 2) == 0);
ViewData["isEvenRow"] = isEvenRow;
Then you will just toggle your CSS in the view based on whether the value is true or false.

Rendering Partial Views in a Loop in MVC3

I have a pretty simple scenario, Model for my view is a List.
Loop through List like
#foreach(CustomObject obj in Model)
{
Html.Partial("_TrackingCustomObject",obj)
}
So i was expecting to have number of partial views according to my list.
Partial View has been developed accordingly.
There is no error on page. It just does not show any data that is supposed to display by partial views.
What is the reason of not showing any data?
You are missing an #:
#foreach(CustomObject obj in Model)
{
#Html.Partial("_TrackingCustomObject", obj)
}
But why writing foreach loops when you can use editor/display templates? Like this:
#model IEnumerable<CustomObject>
#Html.EditorForModel()
and then simply define the corresponding editor template (~/Views/Shared/EditorTemplates/CustomObject.cshtml) that will automatically be rendered for each element of your model:
#model CustomObject
<div>
#Html.EditorFor(x => x.Foo)
</div>
Simple and conventional :-)
You're missing the Razor symbol #:
#foreach(CustomObject obj in Model)
{
#Html.Partial("_TrackingCustomObject",obj)
}
Also make sure your partial view is using the object type CustomObject as the Model.
#model MyProject.Models.CustomObject
<h1>Yeah we're in a partial! #Model.SomeProperty </h1>
To try and drill down to where the error is, try placing some static text inside the PartialView.
<p>Some text</p>
If your collection has 10 items, then you should see 10 of these paragraphs. Next once this works, focus on displaying some property in each item.
#model MyProject.Models.CustomObject
<p>Some text</p>
<p>#Model.SomeProperty</p>
When you are creating html form using #Html.BeginForm() you have to wrap the remaining stuf inside a <div> or other container else the html elements won't get rendered.
Ex.
this won't work
#using(Html.BeginForm())
{
Html.EditorFor(m => m.Name)
}
this will work
#using(Html.BeginForm())
{
<div>
#Html.EditorFor(m => m.Name)
</div>
}
Bit late in the day, but this worked for me in MVC 4:
#foreach (var p in #Model.RelatedCards)
{
Html.RenderPartial("_ThumbPartial", p);
}
Try this:
#Html.RenderPartial("_TrackingCustomObject",obj)
This is too old but someone can use it.
#foreach(CustomObject obj in Model)
{
<text>
Html.Partial("_TrackingCustomObject",obj)
</text>
}

MVC3 #Html.DropDownListFor not populating selected item

I have the following dropdown list in an MVC3 application and the selected item is not showing a value. My dropdown list is contained in a list of a custom class. My view code is as follows.
...
for (int i = 0; i < Model.PartAttributes.Count; i++)
{
if (Model.PartAttributes[i].IsActive)
{
<div class="row inline-inputs">
<label class="span5">#Model.PartAttributes[i].DisplayName:</label>
#Html.DropDownListFor(m => m.PartAttributes[i].Value, Model.PartAttributes[i].PartList, "Choose Part")
#Html.TextBoxFor(model => Model.PartAttributes[i].Value, new { #class = "mini" })
#Html.ValidationMessageFor(model => Model.PartAttributes[i].Value)
#Html.HiddenFor(model => model.PartAttributes[i].AttributeName)
</div>
}
}
...
The text box under the dropdown box fills correctly and the list options fill correctly. And the selected option is in the list of options to pick. What can I be doing wrong?
Try like that:
#Html.DropDownListFor(
m => m.PartAttributes[i].Value,
new SelectList(
Model.PartAttributes[i].PartList,
"Value",
"Text",
Model.PartAttributes[i].Value
),
"Choose Part"
)
AFAIK the DropDownListFor helper is unable to determine the selected value from the lambda expression that is passed as first argument if this lambda expression represents complex nested properties with collections. Works with simple properties though: m => m.FooBar. I know that it kinda sucks, but hopefully this will be fixed in future versions.

Nested edit templates in mvc razor

I have seen many versions of this question but the answers always turn into "you don't need to do that" and never an answer.
I have a list of attributes about a product that I want to show in an unordered list with checkboxes to select particular attributes.
In My Model:
public List<ProductAttribute> ProductAttributes {get;set;}
in my Create.cshtml:
<div Class="ProductAttributes">
#Html.EditorFor(m => m.ProductAttributes, "ProductAttributeSelectorList")
</div>
In my ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
And finally, in my EditLocationAttributeList.cshtml
#model Models.DisplayLocationAttribute
#Html.HiddenFor(m => m.Id)
#Html.CheckBoxFor(m => m.IsSelected)
<a href="#" alt="#Model.Description" >#Model.Name</a>
This all displays on the page perfectly I can style it like I want with CSS, but when the submit returns, my model.ProductAttributes collection is null.
I know I can bind directly to the EditLocationAttributeList and it will display and return a populated model.ProductAttributes if I use this:
#Html.EditorFor(m => m.ProductAttributes, "EditLocationAttributeList")
but now I do not have the unordered list that I would like to have. I could treat the template like an Item Template and have the line item tags embeded in that template but that seems smelly to have a template that is tightly coupled to another template.
Any Ideas?
Thanks in advance,
Tal
model.ProductAttributes is null, because the DefaultModelBinder is not able to reference each DisplayLocationAttribute back to the ProductAttribute property of your model. The simplest solution is to name your list elements as an array, so that for example each IsSelected element is named in the style ProductAttributes[n].IsSelected.
Add the following to ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
#{
var i = 0;
}
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "ProductAttributes[" +
i.ToString() + "]";
i++;
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
#{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "";
}
This will give you an indexed array, which the DefaultModelBinder will be able to associate to ProductAttributes. However, it builds a hard dependency to the name ProductAttributes. You can get around the hard dependency by several methods, such as passing the property name in the ViewBag.

Index of current item in mvc display template

I have an mvc page with a displaytemplate.
How do I get the index of the current item being rendered in the displaytemplate.
it produces correct bindable results in the name attributes.
<input name="xxx[0].FirstName"/>
<input name="xxx[1].FirstName"/>
I want that index value in the display template. Is it in the ViewContext somewhere?
#* page.cshtml #
#model ...# property Contacts is IEnumerable *#
<table id="contacts" class="editable">
<thead>
<tr><th>Name</th><th>Surname</th><th>Contact Details</th></tr>
</thead>
<tbody>
#Html.DisplayFor(x => x.Contacts)
</tbody>
in the display template we have
#* contact.cshtml *#
#model ...#* This is the T of the IEnumerable<T> *#
<tr>
#* I NEED THE INDEX OF THE CURRENT ITERATION HERE *#
<td>#Html.DisplayFor(x => x.FirstName)</td>
</tr>
I am afraid there is no easy way to get the index. It's buried inside the internal System.Web.Mvc.Html.DefaultDisplayTemplates.CollectionTemplate method and not exposed. What you could use is the field prefix:
#ViewData.TemplateInfo.HtmlFieldPrefix
Another possibility is replace #Html.DisplayFor(x => x.Contacts) with:
#for (int i = 0; i < Model.Contacts.Length; i++)
{
#Html.DisplayFor(x => x.Contacts[i], new { Index = i })
}
and then inside the template #ViewBag.Index should give you the current index but I must admit it's kinda ugly.
Depending on why you need the index, there may be an easier way.
I was doing something similar and wanted the index just so I could easily number the list of items such as:
First items in List
Second item in List
etc.
The maybe obvious solution was to render my display template as a List Item within an Order List, and let the browser handle the display of the index. I was originally doing this in a table and needed the index to display in the table, but using a ordered list makes it much easier.
So my parent view had the following:
<ol>
#Html.DisplayFor(m => m.Items)
</ol>
And my template wrote on the items in an list item using divs - note you could use floating divs, or better yet display: inline-style to get a table like effect
<li>
<div class="class1">#Html.DisplayFor(m => m.ItemType)</div>
...
</li>
And the css:
.class1 {
display: inline-block;
width: 150px;
}
Extending #DarinDimitrov's answer, I wanted to submit a complete solution:
Extention:
namespace ApplicationName.Helpers
{
public static class RazorHtmlHelpers
{
public static Int32 GetTemplateIndex(this TemplateInfo template)
{
var index = 0;
var match = Regex.Match(template.HtmlFieldPrefix, #"\d");
Int32.TryParse(match.Value, out index);
return index;
}
}
}
Parent View Markup:
#Html.DisplayFor(model => model.Items)
Template Markup:
#using ApplicationName.Helpers
#model ApplicationName.Models.ModelName
<span>Item #ViewData.TemplateInfo.GetTemplateIndex()</span>

Resources