I've got a viewmodel for a page where fields are combined into fieldsets.
The VM looks like this:
public class FieldsetVM
{
public Int64 ID { get; set; }
public string Name { get; set; }
[Display(Name = "Available Fields")]
public ICollection<SelectListItem> AvailableFields { get; set; }
[Display(Name = "Current Fields")]
public ICollection<SelectListItem> UsedFields { get; set; }
public FieldsetVM(int id, string name, List<Field> availFields, List<Field> usedFields)
{
this.ID = id;
this.Name = name;
this.AvailableFields = new List<SelectListItem>();
foreach (Field field in availFields)
this.AvailableFields.Add(new SelectListItem { Text = string.Format("{0} ({1})", field.Name, field.FieldType.ToString()), Value = field.FieldID.ToString() });
this.UsedFields = new List<SelectListItem>();
foreach (Field field in usedFields)
this.UsedFields.Add(new SelectListItem { Text = string.Format("{0} ({1})", field.Name, field.FieldType.ToString()), Value = field.FieldID.ToString() });
}
public FieldsetVM()
{
}
}
Get in the controller looks like this:
[HttpGet]
public ActionResult Create()
{
FieldsetVM vm = new FieldsetVM(0, "", uw.FieldRepo.Get().ToList(), new List<Field>());
return View(vm);
}
Relevant piece of the view looks like this:
<div class="col-md-3 col-xs-6">
<div class="editor-label">
#Html.LabelFor(m => m.AvailableFields)
</div>
<div class="editor-field">
#Html.ListBoxFor(m => m.AvailableFields, Model.AvailableFields)
</div>
<button type="button" onclick="moveSelected('AvailableFields','UsedFields');">Move Selected</button>
</div>
<div class="col-md-3 col-xs-6">
<div class="editor-label">
#Html.LabelFor(m => m.UsedFields)
</div>
<div class="editor-field">
#Html.ListBoxFor(m => m.UsedFields, Model.UsedFields)
</div>
<button type="button" onclick="moveSelected('UsedFields','AvailableFields');">Remove Selected</button>
</div>
A tiny bit of JavaScript wires up the two listboxes:
function moveSelected(firstSelectId, secondSelectId) {
$('#' + firstSelectId + ' option:selected').appendTo('#' + secondSelectId);
$('#' + firstSelectId + ' option:selected').remove();
}
And then I have a POST in the controller:
[HttpPost]
public ActionResult Create(FieldsetVM postedVm)
{
Fieldset fs = new Fieldset();
fs.Name = postedVm.Name;
if (fs.Fields == null)
fs.Fields = new List<Field>();
fs.Fields.Clear();
foreach (SelectListItem item in postedVm.UsedFields)
fs.Fields.Add(uw.FieldRepo.GetByID(item.Value));
uw.FieldsetRepo.Insert(fs);
return RedirectToAction("Index");
}
My expectation is that in the postedVm, we would be able to see the values the user selected into UsedFields. Instead, UsedFields and AvailableFields are ALWAYS blank when the user posts back to the HttpPost Create() action.
I'm trying to figure out why: Surely moving items between list boxes is a fairly common way to configure things? Shouldn't MVC take a look at the values in the generated and use them to populate the postedVm object?
EDIT Based on feedback from best answer, here is my revised Create/Post action.
[HttpPost]
public ActionResult Create(FieldsetVM postedVm, int[] UsedFields)
{
Fieldset fs = new Fieldset();
fs.Name = postedVm.Name;
fs.Fields = new List<Field>();
foreach (int id in UsedFields)
fs.Fields.Add(uw.FieldRepo.GetByID(id));
uw.FieldsetRepo.Insert(fs);
uw.Save();
return RedirectToAction("Index");
}
When you post the form, only the Ids for AvailableFields and UsedFields will be posted. If you have multiple values, then you'll get a comma seperated list of ids, so modelbinding will not be able to bind those posted Ids to FieldsetVM postedVm.
If you do something like public ActionResult Create(int[] availableFields, int[] usedFields) you should be able to get the selected Ids.
Related
I have a form that posts to an action:
public ActionResult Index()
{
CheckDataVM vm = new CheckDataVM();
vm.SerialNumbers = GetAllSerials();
vm.CustomerNames = GetAllCustomers();
vm.DateFrom = DateTime.Now.AddDays(-1);
vm.DateTo = DateTime.Now;
return View(vm);
}
[HttpPost]
public ActionResult Index(CheckDataVM v)
{
CheckDataVM vm = new CheckDataVM();
vm.SerialNumbers = GetAllSerials();
var s = vm.SerialNumbers.First().Text.ToString();
vm.Channels = GetAllChannels(s);
vm.DateFrom = DateTime.Now.AddDays(-1);
vm.DateTo = DateTime.Now;
return View(vm);
}
In my Razor view, I have post:
#using (Html.BeginForm("Index", "CheckData", FormMethod.Post, new { id = "SerialsForm" }))
{
<div class="card-body" style="font-size: small;">
<div class="form-group">
#Html.DropDownListFor(x => x.SelectedSerial, Model.SerialNumbers, new { #class = "form-control form-control-sm" })
<input type="submit" value="Submit" />
</div>
</div>
}
The view model is:
public class CheckDataVM
{
public string CustomersName { get; set; }
public string SelectedSerial { get;set; }
[Display(Name="Select a serial number")]
public IEnumerable<SelectListItem> SerialNumbers { get; set; }
}
The dropdowns work, but when I submit the form the only thing I get back is the object name (SerialNumbers) as the key.
I want to be able to get the selected item from the dropdown list and pass this to the FormCollection in the Httpost of the Index action. For the life of me, I cannot get it to work!
I am expecting to see a key called 'CustomersDdl' and it's value. For example, if I had a dropdown full of countries and I pick England, I am expecting to see a value come back in the FormCollection saying England.
What am I doing wrong?
The value to postback is depending on how you create "SelectListItem", in your case it is in the method "GetAllSerials()"
vm.SerialNumbers = serialNumbers.Select(serial => new SelectListItem
{
Selected = serial.id == vm.SelectedSerial ? true : false,
Text = serial.Name,
Value = serial.Name
}).ToList();
I am a new to MVC and still learning! I am trying to create a very basic App in my web which allows users to convert money value according to their preference. I made the web APi and was successful to call the service to my forms. However, in my controller I managed to get the currencies (names) to the index view, but cannot post the form back once entering a value in the text box to generate the partial view! What am I doing wrong in my codes?!
Currency Controller
namespace MVC_ATM.Controllers
{
public class CurrencyController : Controller
{
[HttpGet]
// GET: Currency
public ActionResult Index()
{
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll();
SelectList list = new SelectList(listCurrency,"Id", "CurrencyName");
ViewBag.listCurrencies = list;
return View();
}
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (!ModelState.IsValid)
{
string errors = string.Join("<br />", ModelState.Values
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage));
return new ContentResult { Content = errors };
var rate = Convert.ToDecimal(cur.ConversionRate);
if (cur.CurrencyName == cur.CurrencyName)
{
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else if (cur.CurrencyName != cur.CurrencyName)
{
foreach (var currency in cur.CurrencyName)
{
ViewBag.Theresult = rate * cur.Value;
}
return PartialView("_CurrencyValue");
}
}
return View();
}
}
}
Currencies Model
namespace Project.Model
{
public class Currencies
{
public int Id { get; set; }
public string CurrencyName { get; set; }
public string CurrencyCountry {get; set;}
public decimal Value { get; set; }
public string ConversionRate { get; set; }
}
}
Index View
#model Project.Model.Currencies
#{
ViewBag.Title = "Index";
}
<h2>Currency</h2>
<body>
<div class="converter">
Convert: #Html.TextBoxFor(m => m.ConversionRate, new { #size = "5" })
<div class="form-group">
#Html.Label("Convert from", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownList("Currency List", ViewBag.listCurrencies as SelectList, "Please Select a currency")
</div>
</div>
<div class="form-group">
#Html.Label("Convert to", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownList("Currency List", ViewBag.listCurrencies as SelectList, "Please Select a currency")
</div>
</div>
<div>
<button type="submit" class="btn btn-primary">Convert</button>
</div>
</div>
</body>
Couple of things to notice, is the POST action and missing form tag in the view . You created a POST action that accepts Currencies model but the form doesn't post that. Only ConversionRate will bind to the model. To get the "Currency From" and "Currency To" and the "Conversion Rate" you will require a different approach/small changes.
ConversionModel.cs a new Model for index page that will capture your required fields.
public class ConversionModel
{
[Required]//decimal would be better but up to you requirement
public decimal ConversionRate { get; set; }
[Required]
public int FromCurrencyId {get;set;}
public SelectList FromCurrencies {get;set;}
[Required]
public int ToCurrencyId {get;set;}
public SelectList ToCurrencies {get;set;}
}
Get: while there is nothing wrong with what you've done, lets use a model approach and tightly bind it.
public ActionResult Index()
{
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll();
ConversionModel model = new ConversionModel();
model.FromCurrencies = new SelectList(listCurrency,"Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency,"Id", "CurrencyName");
return View(model);
}
Post: Important thing here to notice is the SelectList will not be posted back. Only the ConversionRate, FromCurrencyId and ToCurrencyId are sent back not the Lists. If error occurs you will need to rebuild the lists and send it back in the model.
[HttpPost]
public ActionResult Index(ConversionModel curModel)
{
if(ModelState.IsValid)
{
if(curModel.FromCurrencyId ==curModel.ToCurrencyId)
{
//do something if same currecnies and return.
}
else
{
//Get the currencyList with rates from db
//use currency ToCurrencyId and FromCurrencyId to fetch the 2 currencies
// perform conversion with curModel.ConversionRate with existing logic
}
}
//Don'f forget to rebuild the Select Lists...
return View(curModel);
}
View:
#model Project.Model.ConversionModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("Index", "Currency", FormMethod.Post)
{
#Html.TextBoxFor(m => m.ConversionRate, new { #size = "5" })
#* Please check the syntax *#
#Html.DropDownListFor(m => m.FromCurrencyId , Model.FromCurrencies as SelectList)
#Html.DropDownListFor(m => m.ToCurrencyId , Model.ToCurrencies as SelectList)
<button type="submit" class="btn btn-primary">Convert</button>
}
Not a CUT_COPY_PASTE. please do check for errors if any. It is only an approach.
ajax POST probably the next thing to learn... Let us know.
You need to put your items inside a form like this:
#using (Html.BeginForm("Index", "Currency", FormMethod.Post)
{
// Your form items
}
I'm just venturing out into the world of MVC (v4) and I'm having real trouble getting the actual selected values of a list of radiobutton lists. I'm pulling the data from an Umbraco database (but I guess the principle of what I'm trying to do will be the same) where there are list of questions with each question having a list of answers. I'll post everything that I've done so far in the hope that someone more intelligent than I could point me in the right direction:
Here's my content structure
My Model
namespace ICASolution.Models
{
public class MultipleChoiceViewModel
{
public int iQuestionID { get; set; }
public string zQuestionTitle { get; set; }
public string zQuestionText { get; set; }
public List<Answers> lAnswers { get; set; }
}
public class Answers
{
public int iAnswerID { get; set; }
public string zAnswerText { get; set; }
public bool bCorrect { get; set; }
public string selectedAnswer { get; set; }
}
}
My surface controller:
namespace ICASolution.Controllers
{
public class MultipleChoiceSurfaceController : SurfaceController
{
//
// GET: /MultipleChoiceSurface/
//public ActionResult Index()
//{
// return PartialView("MultipleChoice", new MultipleChoiceViewModel());
//}
[HttpPost]
public ActionResult Grade(MultipleChoiceViewModel model)
{
return RedirectToCurrentUmbracoPage();
}
public ActionResult Index()
{
var TestPage = Umbraco.Content(CurrentPage.Id);
var questions = new List<MultipleChoiceViewModel>();
foreach (var child in TestPage.Children)
{
var questionid = child.Id;
var questiontitle = child.GetPropertyValue("questionTitle");
var questiontext = child.GetPropertyValue("questionText");
questions.Add(new MultipleChoiceViewModel { iQuestionID = questionid, zQuestionTitle = questiontitle, zQuestionText = questiontext, lAnswers = answerList(questionid) });
}
return PartialView("MultipleChoice", questions);
}
public List<Answers> answerList(int iMyQuestionID)
{
var questionPage = Umbraco.Content(iMyQuestionID);
var answers = new List<Answers>();
foreach(var child in questionPage.Children)
{
answers.Add(new Answers { iAnswerID = child.Id, zAnswerText = child.GetPropertyValue("answerTitle"), bCorrect = child.GetPropertyValue("correctAnswer") });
}
return answers;
}
}
}
and finally my partial:
#model IEnumerable<ICASolution.Models.MultipleChoiceViewModel>
<div class="ethicsTestContainer">
<div class="col-md-12">
<div class="col-md-12 noRPadding">
#using (Html.BeginUmbracoForm<ICASolution.Controllers.MultipleChoiceSurfaceController>("Grade")) {
foreach (var item in Model)
{
<div class="form-group">
<p><strong>#item.zQuestionTitle</strong></p>
<p>#item.zQuestionText</p>
#{
foreach (var answerItem in item.lAnswers)
{
<div class="radio radio-danger">
#Html.RadioButton(answerItem.iAnswerID.ToString(), answerItem.iAnswerID, new { #type = "radio", #id = answerItem.iAnswerID, #name = item.iQuestionID })
#*<input type="radio" name="#item.iQuestionID" id="#answerItem.iAnswerID" value="option1">*#
<label for="#answerItem.iAnswerID">
#answerItem.zAnswerText <span> </span>#answerItem.bCorrect
</label>
</div>
}
}
</div>
}
<div class="col-sm-8 col-sm-push-2">
<button type="submit" class="btn btn-default btn-block">CLICK HERE TO COMPLETE YOUR ETHICS TEST</button>
</div>
}
</div>
</div>
</div>
Everything renders fine when displayed to the user:
But I just can't work out how to get the selections that the user has made on the HTTPPOST (basically I need to count the amount of correct answers that they have made).
Your problem is that you are generating the controls for MultipleChoiceViewModel in a foreach loop with generates duplicate name attributes which cannot be bound to a collection (they do not include indexers) and duplicate id attributes which is invalid html. You need to generate the controls in a for loop (or use a custom EditorTemplate for type of MultipleChoiceViewModel)
You also need to move the selectedAnswer property to MultipleChoiceViewModel (not in selectedAnswer)
Using a for loop (the model must be IList<MultipleChoiceViewModel>)
for(int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m[i].iQuestionID) // for post back
#Html.DisplayFor(m => m[i].zQuestionTitle)
...
foreach(var item in Model[i].lAnswers)
{
#Html.RadioButtonFor(m => m[i].selectedAnswer, item.iAnswerID, new { id = item.iAnswerID })
<label for="#item.iAnswerID">#item.zAnswerText</label>
}
}
To use an EditorTempate, create a partial view in /Views/Shared/EditorTemplates named MultipleChoiceViewModel.cshtml
#model yourAssembly.MultipleChoiceViewModel
#Html.HiddenFor(m => m.iQuestionID)
#Html.DisplayFor(m => m.zQuestionTitle)
...
foreach(var item in Model.lAnswers)
{
#Html.RadioButtonFor(m => m.selectedAnswer, item.iAnswerID, new { id = item.iAnswerID })
<label for="#item.iAnswerID">#item.zAnswerText</label>
}
and then in the main view,replace the for loop with
#Html.EditorFor(m => m)
The EditorFor() method will generate the html based on the template for each item in the collection.
Side notes: RadioButtonFor() generates type="radio" so adding a html for #type = "radio" is pointless. And NEVER override the name attribute when using html helpers
You can create the radio buttons using below code:
<div class="mt-radio-inline">
<label class="mt-radio">
<input type="radio" name="q1" value="q1_Chart"> Chart
<span></span>
</label>
<label class="mt-radio">
<input type="radio" name="q1" value="q1_AnySize"> Any Size
<span></span>
</label>
</div>
And get the selected radio button value from code given below:
string q = form["q1"];
In above code form is the object of FormCollection
This will gives you a string of the selected radio button from a group.
I have a page to create objects, and in this I have a DropDownList. If I select an item from the list my page will save correctly, however if I don't select an item it looks like it fails on a postback as the objects will be null.
What I want is to try and validate whether the user has selected an item (default is "Please Select...").
I have code that will check and see in the controller if the item is null, but it's how do I then display a message? Keeping all other details if they exist.
public ActionResult Create(int objectId = 0)
{
var resultModel = new MyObjectModel();
resultModel.AllObjects = new SelectList(_system.GetAllObjects(objectId));
// GetAllObjects juts returns a list of items for the drop down.
return View(resultModel);
}
[HttpPost]
public ActionResult Create(int? objectId, FormCollection collection)
{
try
{
int objectIdNotNull = 0;
if (objectId > 1)
{
objectIdNotNull = (int) objectId;
}
string objectName = collection["Name"];
int objectTypeSelectedResult = 1;
int.TryParse(collection["dllList"], out objectTypeSelectedResult);
if (!Convert.ToBoolean(objectTypeSelectedResult))
{
// So here I have discovered nothing has been selected, and I want to alert the user
return RedirectToAction("Create",
new {ObjectId = objectIdNotNull, error = "Please select an Object Type"});
}
....
return RedirectToAction(...)
}
catch
{
return View();
}
}
The above code just goes to the Create page but doesn't display an error. In my View for Create I have the following line which I assumed would display any errors:
#ViewData["error"]
Additional code
Model:
using System.Collections.Generic;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
namespace MyNameSpace
{
public class MyObjectModel
{
[Required(ErrorMessage = "Please select an Object Type")]
public SelectList AllObjects { get; set; } // I populate the drop down with this list
}
}
View:
#model MyNameSpace.MyObjectModel
#{
ViewBag.Title = "Create";
}
<h2>Create </h2>
<p class="text-error">#ViewData["Message"]</p>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"> </script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.MyObject.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(model=>model.MyObjectType.Name, new {style="width: 750px"})
#Html.ValidationMessageFor(model => model.MyObjectType.Name)
</div>
<div>
<label for="ddlList">Choose Type</label>
#if (#Model != null)
{
#Html.DropDownList("ddlList", Model.AllObjects, "Please Select...")
#Html.ValidationMessageFor(model => model.AllObjects, "An object must be selected", new { #class = "redText"})
}
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
You are validating the SelectList which is wrong
[Required(ErrorMessage = "An object must be selected")]
public SelectList AllObjects { get; set; }
Your Model should be
[Required(ErrorMessage = "Please select an Object Type")]
public int ObjectId { get; set; }
public string ObjectName { get; set; }
Your Controller(no need for form collection thats the whole point of MVC)
public ActionResult Create(int Id = 0)
{
MyObjectModel resultModel = new MyObjectModel();
var ObjectResultList = _system.GetAllObjects(Id);
var ObjectSelectList = new SelectList(ObjectResultList, "id", "Name");
ViewBag.ObjectList = ObjectSelectList;
return View(resultModel);
}
Your Post controller:
[HttpPost]
public ActionResult Create(MyObjectModel o)
{
try
{
if (ModelState.IsValid)
{
//It's valid , your code here!
return RedirectToAction("ObjectCreated", new { id = o.objectId });
}
else
{
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => new { x.Key, x.Value.Errors })
.ToArray();
}
}
}
catch (Exception ex)
{
Response.Write(ex.InnerException.Message);
}
//If we get here it means the model is not valid, We're in trouble
//then redisplay the view repopulate the dropdown
var ObjectResultList = _system.GetAllObjects(objectId);
var ObjectSelectList = new SelectList(ObjectResultList, "id", "value");
ViewBag.ObjectList = ObjectSelectList;
return View(o);
}
Your View should be strongly Typed
<div class="editor-label">
#Html.LabelFor(model => model.ObjectId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ObjectId,
(IEnumerable<SelectListItem>)ViewBag.ObjectList, "-- Select One Object --")
#Html.ValidationMessageFor(model => model.ObjectId)
</div>
I have the following cshtml form
#using (Html.BeginForm(Html.BeginForm("Create", "UserRole", Model, FormMethod.Post)))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Role</legend>
<div class="editor-label">
#Html.Label(Model.User.UserName)
</div>
<div class="editor-field">
#Html.CheckBoxList(Model.CheckboxList)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And I wish to get the Model.CheckboxList selected Items in my action.
I have the following Create Action in my Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(UserRoleViewModel userRoleViewModel)
{
if (ModelState.IsValid)
{
//_context.Role.Add(role);
//_context.SaveChanges();
//return RedirectToAction("Index");
}
return View(viewModel);
}
However the viewModel.CheckboxList is 0.
How can I pass the selected values of the checkboxlist, and also the Model.User to the Controller Action?
My ViewModel looks like this :-
public User User { get; set; }
public IEnumerable<Role> RoleList { get; set; }
public List<UserRoleViewModel> UserList { get; set; }
public IEnumerable<SelectListItem> CheckboxList { get; set; }
public UserRoleViewModel()
{
}
public UserRoleViewModel(User user, IEnumerable<Role> roleList )
{
User = user;
RoleList = roleList;
}
Thanks for your help and time!
UPDATE ----------- After reading this post enter link description here, I tried to adapt my code to follow the example, but I am still finding problems with this updated code.
Now I have the following :-
cshtml :-
#model IEnumerable<MvcMembership.ViewModels.RoleCheckboxListViewModel>
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="OK" />
}
Views/Role/EditorTemplates/RoleCheckboxListViewModel.cshtml
#model MvcMembership.ViewModels.RoleCheckboxListViewModel
#Html.HiddenFor(x => x.RoleId)
#Html.HiddenFor(x => x.RoleName)
<div>
#Html.CheckBoxFor(x => x.Checked)
#Html.LabelFor(x => x.Checked, Model.RoleName)
</div>
ViewModels :-
public class RoleCheckboxListViewModel
{
public string RoleId { get; set; }
public string RoleName { get; set; }
public bool Checked { get; set; }
}
and the controller action is as follows :-
public ActionResult Create(int? uid)
{
var checkBoxList = new[]
{
new RoleCheckboxListViewModel() {
RoleId = "1", Checked = true, RoleName = "item 1" },
new RoleCheckboxListViewModel() {
RoleId = "2", Checked = true, RoleName = "item 2" },
new RoleCheckboxListViewModel() {
RoleId = "3", Checked = true, RoleName = "item 3" },
};
return View(checkBoxList);
}
The problem I have now is that on the Create.cshtml. I cannot see the checkboxlist, but only 123 displayed as well as the OK button.
Any help would be very much appreciated cause I am at a dead end at the moment.
I've accomplished this with the following parts:
1) A view model for the child element that adds the bool property that will represent whether or not the checkbox is checked in the View later... ie:
public class CategoryViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public bool Assigned { get; set; }
}
2) A view model for the parent element that adds a collection property for this new child element view model, ie:
public class ManufacturerViewModel
{
public Manufacturer Manufacturer { get; set; }
public IList<CategoryViewModel> Categories { get; set; }
public ManufacturerViewModel()
{
Categories = new List<CategoryViewModel>();
}
}
3) A service layer method for getting a list of all child elements, while also setting the bool property for each ("Assigned" in my example). To be used by your controller.
public IList<CategoryViewModel> GetCategoryAssignments(Manufacturer mfr)
{
var categories = new List<CategoryViewModel>();
foreach (var category in GetCategories())
{
categories.Add(new CategoryViewModel
{
ID = category.ID,
Name = category.Name,
Assigned = mfr.Categories.Select(c => c.ID).Contains(category.ID)
});
}
return categories;
}
4) A method for updating the parent item's collection based on your checkboxlist selections. To be used by your controller.
public void UpdateCategories(string[] selectedCategories, ManufacturerViewModel form)
{
if (selectedCategories == null)
selectedCategories = new string[] { };
var selectedIds = selectedCategories.Select(c => int.Parse(c)).ToList();
var assignedIds = form.Manufacturer.Categories.Select(c => c.ID).ToList();
foreach (var category in GetCategories())
{
if (selectedIds.Contains(category.ID))
{
if (!assignedIds.Contains(category.ID))
form.Manufacturer.Categories.Add(category);
}
else
{
if (assignedIds.Contains(category.ID))
form.Manufacturer.Categories.Remove(category);
}
}
}
5) Modifications to your Create/Edit view. ie:
#Html.EditorFor(model => model.Categories)
You must also add this so that the original assigned values are included in post data. You'll have to add a HiddenFor for each property that you have set as Required through validation.
for (int i = 0; i < Model.Manufacturer.Categories.Count; i++)
{
#Html.HiddenFor(model => model.Manufacturer.Categories[i].ID);
#Html.HiddenFor(model => model.Manufacturer.Categories[i].Name);
}
6) And finally, a new EditorTemplate for your child view model element. ie:
#model YourProject.ViewModels.CategoryViewModel
<li>
<input type="checkbox"
id="#string.Format("cb{0}{1}", #Model.Name, #Model.ID)"
name="selectedCategories" //Notice this name corresponds to string[] selectedCategories so that it can be extracted from the post data
value="#Model.ID"
#(Html.Raw(Model.Assigned ? "checked=\"checked\"" : "")) />
<label for="#string.Format("cb{0}{1}", #Model.Name, #Model.ID)">#Model.Name</label>
#Html.HiddenFor(model => model.ID)
</li>
Hopefully my own application gives you a better idea of how to solve this issue.
Store your selected value into the variable as follows, and pass it to an hidden field, then you can access it easily
var modelSelected = document.getElementById("modelName");
document.getElementById('selectedModel').value =
modelSelected.options[modelSelected.selectedIndex].text;
<input id="selectedModel" name="selectedModel" type="hidden" runat="server" />