Saving unknown number of identical Dropdowns on a Razor view - c#

I've followed Steve Sanderson’s "Editing a variable length list, ASP.NET MVC 2-style" guide and created an MVC view to edit a list of items. Note i'm using MVC 3 so i'm not sure if there is a better way to do this. http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
The problem i have is one of the fields on my list is a dropdown list. i've managed to get the dropdown populated on each row but its not loading the selected value when the page loads. It is however saving the selected value but every time i edit the page i need to re-set all the dropdowns.
Does anyone know how to set the selected dropdown value for each "row" on the partial view?
My edit view has
#foreach (var item in Model.Roles)
{
#Html.Partial("RoleRow-Edit", item)
}
My partial view has
#using (Html.BeginCollectionItem("Roles"))
{
#Html.EditorFor(model => model.TemplateID)
#Html.DropDownList("PartyRoleID", (SelectList)ViewBag.PartyRoles)
#Html.EditorFor(model => model.DisplayName)
}
On my controller i have
ViewBag.PartyRoles = new SelectList(db.PartyRoles.OrderBy(c => c.Role), "Role", "Role");

I found a workaround : you should create the SelectList in the partial view, and set its intial value to the bounded value, so partial view will look like this :
#{var selectList = new SelectList(db.PartyRoles.OrderBy(c => c.Role),
"Role", "Role",Model.PartyRoleID);}
/*pass the db.PartyRoles.OrderBy(c => c.Role) in view bag inside controller,
it's cleaner*/
#using (Html.BeginCollectionItem("Roles"))
{
#Html.EditorFor(model => model.TemplateID)
#Html.DropDownList(model => model.PartyRoleID, selectList)
#Html.EditorFor(model => model.DisplayName)
}

I can't see anywhere in your code where you would set the PartyRoleID value. I would recommend you to use view models and strongly typed views instead of ViewBag:
#Html.DropDownListFor(x => x.PartyRoleID, Model.PartyRoles)
Now all you have to do on your controller is to set the value of your view model:
var roles = db.PartyRoles.OrderBy(c => c.Role);
var model = new MyViewModel();
// preselect the value of the dropdown
// this value must correspond to one of the values inside the roles collection
model.PartyRoleID = "admin";
model.PartyRoles = new SelectList(roles, "Role", "Role");
return View(model);
Also I would recommend you using Editor Templates and replace the following foreach loop:
#foreach (var item in Model.Roles)
{
#Html.Partial("RoleRow-Edit", item)
}
By the following:
#Html.EditorFor(x => x.Roles)
Now all that's left is to customize the corresponding editor template.

Related

Hidden field not storing value from partial view into an array of complex object

I am unable to get an array of a complex type in the model to store its value IsAssigned between two actions in a controller.
Once the model has been composed, which including the array of complex type (which include IsAssigned), the view engine begins to render the HTML/Razor script. In the view, there is a section of the script that handles the displaying of the controls which manipulates the complex type. It is a "For i" loop that cycles through the array of complex type.
During this loop, there is a razor command HiddenFor for property IsAssigned which is contained inside of the complex type array.
A second step is carried out in the loop that renders a partial view. It is in this view where there are two radio buttons linked to an array position's IsAssigned boolean? property. If the user select yes the property turns to true, and no false.
After the view has been completely rendered and the user has made their selections, the next action method in the controller is activated and the model is passed to it. It is here where I expect the user choices against the IsAssigned property to persist, but no. It does not.
Below is the code for looping and the code for the partial view.
Loop
#for (int i = 0; i < Model.KitTypeAssignment.Length; i++)
{
#Html.HiddenFor(m => m.KitTypeAssignment[i].IsKitTypeActiveForSubject)
#Html.HiddenFor(m => m.KitTypeAssignment[i].KitType.Code)
#Html.HiddenFor(m => m.KitTypeAssignment[i].KitType.Description)
#Html.HiddenFor(m => m.KitTypeAssignment[i].KitType.KitTypeId)
#Html.HiddenFor(m => m.KitTypeAssignment[i].IsAssigned, new { #Name = "IsAssigned" + Model.KitTypeAssignment[i].KitType.Code })
#Html.Partial("_KitTypeDispensing", Model.KitTypeAssignment[i])
}
PartialView
<div class="field inline spacer20">
#{
var kitTypeAssign = String.Format("{0}{1}", Model.KitType.Code, "Assign");
var identityOfAssigned = new { id = #kitTypeAssign, #Name = "IsAssigned" + Model.KitType.Code };
var kitTypeNoAssign = String.Format("{0}{1}", Model.KitType.Code, "NoAssign");
var identityOfNoAssigned = new { id = #kitTypeNoAssign, #Name = "IsAssigned" + Model.KitType.Code };
var sectionTitle = string.Format("{0} {1}", PromptText.Assign_185B7133DB22230701A857C059360CC2.ToLocalizableString(), Model.KitType.Description);
#Html.Label(sectionTitle, new { #class = "colon" })
#Html.RadioButtonFor(m => m.IsAssigned, true, #identityOfAssigned)
<label for=#kitTypeAssign>#Html.PromptFor(PromptText.Yes_93CBA07454F06A4A960172BBD6E2A435)</label>
#Html.RadioButtonFor(m => m.IsAssigned, false, #identityOfNoAssigned)
<label for=#kitTypeNoAssign>#Html.PromptFor(PromptText.No_BAFD7322C6E97D25B6299B5D6FE8920B)</label>
}
</div>
I think the problem is that the partial view does not prefix the names of its HTML input elements correctly, therefore the ModelBinder does not know how to bind them back to your array. Try to pass the correct prefix explicitly to the partial:
#Html.Partial(
"_KitTypeDispensing",
Model.KitTypeAssignment[i],
new ViewDataDictionary { TemplateInfo = new TemplateInfo {
HtmlFieldPrefix = Html.NameFor(m => m.KitTypeAssignment[i]).ToString() }
}
)
Further reading: ASP.NET MVC Partial Views with Partial Models

Passing back a single object when using EditorFor(model[])

This has been a thorn in my side for a while. If I use EditorFor on an array of objects and the editor Template has a form in it ex.
public class FooController:Controller {
public ActionResult Action(Foo foo) {
// ...
}
}
Index.cshtml
#model IEnumerable<Foo>
#Html.EditorFor(m=> m)
EditorTemplate
#model Foo
#using (Html.BeginForm("action", "controller"))
{
#Html.TextBoxFor(f=> f.A)
#Html.CheckBoxFor(f=> f.B)
#Html.LabelFor(f=> f.B)
}
So I'll hit a few problems.
The checkbox label's for doesn't bind correctly to the checkbox (This has to do with the label not receiving the proper name of the property ([0].A as opposed to A).
I'm aware I can get rid of the pre- text by doing a foreach on the model in Index but that screws up ids and naming as the framework doesnt realize there are multiples of the same item and give them the same names.
For the checkboxes I've just been doing it manually as such.
#Html.CheckBoxFor(m => m.A, new {id= Html.NameFor(m => m.A)})
<label for="#Html.NameFor(m => m.A)">A</label>
However I cant solve the inability of the controller to accept the item as a single model. I've even tried allowing an array of Foo's in the Action parameters but that only work when its the first item being edited ([0]...) if its any other item in the array (ex. [1].A) the controller doesn't know how to parse it. Any help would be appreciated.
Make your model a class with the properties you need.
create a class in your Models subfolder
public class MyModel {
public IEnumerable<Foo> Foolist { get ; set;}
public string Something { get;set;}
}
your EditorFor will have to have a foreach loop for Foolist...
MVC will attempt to put your model together from the form and return it to your POST action in the controller.
Edit:
You could create an EditorTemplate for foo. In Views/Shared/EditorTemplates folder, create FooTemplate.cs
#model Foo
<div class="span6 float-left" style="margin-bottom: 6px">
#Html.TextBoxFor(m => m.A, new { style = "width:190px" })
#Html.CheckBoxFor(m => m.B, new { style = "width:40px" })
#Html.ValidationMessage("foo", null, new { #class = "help-inline" })
</div>
then in your view
#foreach (var myFoo in Model)
{
#EditorFor(myFoo)
}
This still suffers from the "model gets passed back as a whole" requiredment of yours. Not sure about why there is a need to process these individually.
Hah finally solved this - Here's how I did it. As a bit of background HTML forms use the name attribute when submitting forms, but the label for element uses Id . so I only adapt the id tag to have the prefix and not the name tag.
--In the cshtml file
#{
var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
ViewData.TemplateInfo.HtmlFieldPrefix = "";
}
then I can specify the id for the properties by their prefix while letting the name remain the same like so
#Html.CheckBoxFor(m => m.A,
new {id = prefix+"."+ Html.NameFor(m => m.A)})
<label for="#prefix.#Html.NameFor(m => m.A)">A!</label></div>

ASP.NET MVC DropDownListFor doesn't set selected data to true in List

I have a view which has a model that is an IEnumerable. I use DropDownListFor Html helper in a foreach loop to output dropdown lists. But it doesn't set the selected item to true. Code as below:
#model IEnumerable<Example>
#foreach (var item in Model) {
#Html.DropDownListFor(modelItem => item.FilePath, (IEnumerable<SelectListItem>)ViewBag.ConfigFiles, string.Empty, null)
}
The above code output a Html select element. But none of the options are selected even though the item.FilePath has the same value as one of the options.
This is an unfortunate limitation of using DropDownListFor() in a loop, and you need to generate a new SelectList in each iteration. However, your use of a foreach loop to generate the form controls will not work. Its creating duplicate name attributes which have no relationship to your model therefore will not bind, and its also generating duplicate id attributes which is invalid html.
Change your model to IList<T> and use a for loop and generate a new SelectList in each iteration using the constructor that sets the selectedValue
#model IList<Example>
....
#for(int i = 0; i < Model.Count; i++)
{
#Html.DropDownListFor(m => m[i].FilePath, new SelectList(ViewBag.ConfigFiles, "Value", "Text", Model[i].FilePath), string.Empty, null)
}
Note that this now generate name attributes which binds to your model
<select name="[0].FilePath">....<select>
<select name="[1].FilePath">....<select>
.... etc
Note that its not necessary to create IEnumerable<SelectListItem> in the controller. Your could instead assign a collection of your objects to ViewBag
ViewBag.ConfigFiles = db.ConfigFiles;
and in the view
new SelectList(ViewBag.ConfigFiles, "ID", "Name") // adjust 2nd and 3rd parameters to suit your property names

MVC (Razor) Filter DropDownList

I'm new to MVC and JavaScript and I have a question about DropDownList.
I Build a program of "Car Renting" and I have a View that allow adding new Car to the inventory.
The view include 2 dropdownlists (see below):
<div class="editor-field">
#Html.DropDownList("ManufacturerId", string.Empty)
#Html.ValidationMessageFor(model => model.ManufacturerId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ModelId)
</div>
<div class="editor-field">
#Html.DropDownList("ModelId", string.Empty)
#Html.ValidationMessageFor(model => model.ModelId)
</div>
I want that when user select a manufacturer for example (Alfa Romeo) the "model" list box will display only "Alfa Mito" models and not the full list...
My Controller function send to the view the Model List using ViewBag see below:
public ActionResult AddNewCar()
{
ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "ManufacturerName");
ViewBag.ModelId = new SelectList(db.Models, "ModelId", "ModelName");
ViewBag.BranchId = new SelectList(db.Branchs, "BranchId", "BranchName");
return View();
}
Please advice.
Thanks,
Almog
call below action method from jquery on dropdown change event.
public List<carmodels> PopulateState(int manufacturerId)
{
var carmodelsObj = (from st in dc.Carmodels
where st.manufacturerId.Equals(manufacturerId)
select st).ToList();
return (carmodelsObj);
}
You need to repopulate/populate your ModelId dropdown on every time when your first dropdown changes (using jquery). Do following:
Get the ManufactureId from first dropdown on change event. and place an ajax call to repopulate your second dropdown.
$("#ManufactureId").change(function() {
var manufacturerId = manufacturer$('#ManufacturerId').val();
$.ajax({url: "~/Car/GetCarsByManufacturer", success: function(result){
//re populate your second dropdown here
//hint: you may use response.id for value
}});
});
Create a function in your controller that returns Car Models, based on selected. (call this function in previous step using ajax).
public List<carmodels> GetCarsByManufacturer(int manufacturerId)
{
var carmodelsObj = (from st in dc.Carmodels
where st.manufacturerId.Equals(manufacturerId)
select st).ToList();
return (carmodelsObj);
}

Foreach in BeginForm and display elements in RadioButtonFor() element

I'm using C#.NET MVC3 (Razor) to create a simple form. But in that form i need to print the content of a list in the form of radiobuttons. But i'm not sure how this works:
#using(Html.BeginForm("Method", "Controller", FormMethod.Post))
{
foreach (Items item in Model.Items)
{
<div>
#Html.RadioButtonFor(item->itemId)
#Html.LabelFor(item->description)
</div>
}
}
But this doesn't work.
I can probably use normal html tags to create the radio buttons. But then the data won't be auto saved in the right?
How can i make this work?
I would recommend you using editor templates instead of writing those loops:
#model MyViewModel
#using(Html.BeginForm("Method", "Controller", FormMethod.Post))
{
#Html.EditorFor(x => x.Items)
}
and now define a corresponding editor template which will automatically be rendered for each element of the model (~/Views/Shared/EditorTemplates/ItemViewModel.cshtml):
#model ItemViewModel
<div>
#Html.RadioButtonFor(x => x.itemId, "1")
#Html.LabelFor(x => x.description)
</div>
Notice that you must pass a second argument to the Html.RadioButtonFor helper in addition to the lambda expression that picks the corresponding view model property. This argument represents the value that will be sent to the server and bound to the corresponding property if the user checks this radio button when the form is submitted.
Also notice that this works by convention. If we assume that the Items property in your main view model is of type IEnumerable<ItemViewModel> then the corresponding editor template that you must define and which will be rendered for each element of this collection is ~/Views/Shared/EditorTemplates/ItemViewModel.cshtml or ~/Views/CurrentController/EditorTemplates/ItemViewModel.cshtml if you don't want this template to be shared between multiple controllers.
As you are inside foreach loop. following will work.
foreach (Items item in Model.Items)
{
<div>
#Html.RadioButtonFor(item.itemId)
#Html.LabelFor(item.description)
</div>
}
If you want to save them all you need to implement zero index based solution as follows
#{int i = 0}
foreach (Items item in Model.Items)
{
<div>
#Html.RadioButtonFor(model =>model[i].itemId)
#Html.LabelFor(model =>model[i].description)
</div>
i++;
}
The syntax is a bit different:
#Html.RadioButtonFor(model => item.ItemId)
#Html.LabelFor(model => item.Description)
Everything else looks fine.
[EDIT] Whoa, I must be tired. Yeah, the following looks just fine, but only for display. Check the Darin's answer for editor template.
[EDIT2] It's not quite obvious from the question, but it seems from your comments that the item in the foreach is another Enumerable. Nest the foreach loops then, to display the properties:
#foreach(var itemList in Model.Items)
{
foreach(var item in itemList)
{
<div>
#Html.RadioButtonFor(model => item.ItemId)
#Html.LabelFor(model => item.Description)
<div>
}
}
Is that it? I'm still not sure if I understand it correctly. :)

Categories

Resources