ASP.NET MVC variable number of dropdown lists - asp.net-mvc-4

Consider the following question and potential answer: ASP.Net MVC Multiple Drop Downs, Single List, Only Allow Unique Selections
Ignoring most of the details, we can see that we can implement many dropdowns for a 1-m relationship like this:
<%: Html.DropDownListFor(model => model.DropwdownId1,Model.DropdownEntities) %>
<%: Html.DropDownListFor(model => model.DropwdownId2,Model.DropdownEntities) %>
<%: Html.DropDownListFor(model => model.DropwdownId3,Model.DropdownEntities) %>
DropdownId1, DropdownId2 and DropdownId3 are properties easily added to the model if and only if we know exactly how many dropdowns we are going to display and postback.
However, I would like to implement a variable number of dropdowns. New dropdowns could, for example, be added dynamically by Javascript. Or the number of dropdowns displayed into the view could be dependent on some variable Property in model. say, model.NumberOfDropDowns.
How can I implement this? How do I write a viewmodel, and controller action that can handle a variable number of dropdowns?
I have done a lot of reading on complex things like editor templates and blog posts where form elements are added dynamically, but I'm really having difficulty trying to figure out how this could be done. Any assistance would be greatly appreciated

A drop-down input still has only 1 submitted form value, so it's the same as any other variable-length view-model property: use a List<T>, like so:
ViewModel
class FooViewModel {
public List<String> DropDownFields { get; set; }
}
Controller Actions
[HttpGet]
ActionResult Index() {
ViewData["dropDownSource"] = new List<SelectListItem>
{
new SelectListItem
{
Text = "Test",
Value= "1"
},
new SelectListItem
{
Text = "Text",
Value= "2"
}
};
return View( new FooViewModel() );
}
[HttpPost]
ActionResult Index(FooViewModel vm) {
for(int i = 0; i < vm.DropDownFields.Count; i++) {
// for each dropdown
}
}
View (aspx syntax)
<% for(int i = 0; i < vm.DropDownFields.Count; i++) { %>
<%= Html.DropDownListFor( m => m.DropDownFields[i], (IEnumerable<SelectListItem>)ViewData["dropDownSource"] ) %>
<% } %>

Related

Select dropdown value after post

I was hoping for some guidance on an issue I am having with preserving the value in a dropdownlist after post (razor)
I have a simple page:
#model testContingency.Models.ListByWardDD
#{
ViewBag.Title = "TestDropDowns";
}
<h2>TestDropDowns</h2>
<div>
#Html.DropDownList("HospModel", Model.Hospital, new { #onchange = "ChangeHospital(this.value)" })
#Html.DropDownList("WardModel", Model.Wards)
<script type="text/javascript">
function ChangeHospital(val) {
window.location.href = "/PatientListByWardDD/TestDropDowns?hospID=" + val;
}
</script>
</div>
here's the controller
public ActionResult TestDropDowns(int? hospID)
{
PASInpatientRepository pasRepo = new PASInpatientRepository();
var returnModel = new ListByWardDD();
var HospitalData = pasRepo.GetPatientHospitalsEnum();
returnModel.Hospital = pasRepo.GetHopspitalListItems(HospitalData);
var WardData = pasRepo .GetPatientWardsEnum(hospID);
returnModel.Wards = pasRepo.GetWardListItems(WardData);
ViewBag.HospSearch = hospID;
return View(returnModel);
}
In the controller PASInpatientRepository() communicates with a cache database. It passes back public IEnumerable < SelectListItem > GetHopspitalListItems. It calls stored procedures written within a cache database (same as sql stored procedures in essence). This is all working fine in its own crude way.
The issue I am having is that when I select the dropdownlist #Html.DropDownList("HospModel", Model.Hospital, new { #onchange = "ChangeHospital(this.value)" }) and the controller is called to refresh the Wards dropdown, I want to preserve the value I have selected in the hospital dropdown. I have tried a few different ways, but I admit, I'm a bit stuck. Most examples I found are for strongly typed.
As I mentioned, I'm new to MVC, but any advice on how to solve this issue, or suggestions on improving my code are greatly appreciated.
So I'm not sure what the Hospital property looks like but I'll make the assumption that each one has a unique ID.
Furthermore to bind the posted data to the view model you'll need to use forms in your view. To create the drop down list use the DropDownListFor-Helper. This way the data will be bound back to your Model after submitting the form.
So your view could look something like this
#model testContingency.Models.ListByWardDD
#{
ViewBag.Title = "TestDropDowns";
}
<h2>TestDropDowns</h2>
<div>
#using (Html.BeginForm("TestDropDowns", "YourController", FormMethod.Post))
{
#Html.DropDownListFor(x => x.HospitalID, Model.Hospital)
#Html.DropDownListFor(x => x.WardID, Model.Wards)
<input type="submit" value="send" />
}
</div>
Your ViewModel testContigency.Models.ListByWardDD must have at least the following properties
public class ListByWardDD {
public int HostpitalID { get;set; }
// the value of the SelectListItem-objects should be the hospital ID
public IEnumerable<SelectListItem> Hospital { get;set; }
public int WardID { get;set; }
// the value of the SelectListItem-objects should be the ward ID
public IEnumerable<SelectListItem> Wards { get;set; }
}
Once you post the form (for simplicity I added a button to send the form and left the javascript part out) the method TestDropDowns of your controller (which you need to fill in the BeginForm-Helper) will be called. That method expects expects an object of type ListByWardDD as a parameter and the framework will automatically populate the values for you.
[HttpPost]
public ActionResult TestDropDowns(ListByWardDD viewModel) {
// your code here, viewModel.HospitalID should contain the selected value
}
Note: After submitting the form the properties Hospital and Wards will be empty. If you need to display the form again, you need to repopulate those properties. Otherwise your dropdown lists are empty.
I tried my best to post valid code but I did not compile or test it.

Why sometime dropdownlist value can not be retrieved in model

I have a form that use DropDownListFor that are set in mvc model. But strangely enough, the current value of them sometimes can not be retrieved in model (sometimes they can). The code is,
<% using (Html.BeginForm("EditPageSubmit", "Home", FormMethod.Post))
{
<%: Html.DropDownListFor(model => model.Destination, Model.Destinations, Model.Destination)%>
...
model.Destinations= (from r in this._repository.Destinations
select new SelectListItem
{
Text = r.Name,
Value = SqlFunctions.StringConvert((double)r.DestinationID),
Selected = false
});
Model.Destination = "...";
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditPageSubmit(FormCollection collection)
{
var updatedModel = new SalesViewModel();
if (TryUpdateModel(updatedModel))
...
}
Strangely enough, sometimes (often when dropdownlist is not changed and remains with initial value), updatedModel.Destination is null, and sometimes it is the selected value. I have really no idea on this problem and need help on it. Thanks.
I find the reason. The initialization variable can not be the same as model id. In this case, simply change to Html.DropDownListFor(model => model.Destination, Model.Destinations, Model.Destination1) will work.

Multiple radio button groups in MVC 4 Razor

I need to have multiple radio button groups in my form like this:
I know it's simply done by specifying the same "name" html attribute for each group.
HOWEVER
MVC doesn't let you specify your own name attribute when using html helper like this:
#Html.RadioButtonFor(i => item.id, item.SelectedID, new { Name = item.OptServiceCatId })
Because it looks at each tag's "name" attribute (not "id") to map/bind the form to the model which the controller receives, etc.
Some said that specifying each with the same "GroupName" attribute will solve the problem, but it didn't work either.
So, is there any way which works ?
EDIT:
Here's my view (simplified):
#model Service_Provider.ViewModels.SelectOptServicesForSubServiceViewModel
#foreach (var cat in Model.OptServices)
{
//A piece of code & html here
#foreach (var item in cat.OptItems.Where(i => i.MultiSelect == false))
{
#Html.RadioButtonFor(i => item.id, item.SelectedID, new { GroupName = item.OptServiceCatId })
<br />
}
}
NOTE:
My model is a List<OptServices>:
public List<OptServices> Cats {get; set;}
And OptServices has a List of OptItems inside:
public class OptServices
{
//a few things
public List<OptItems> Items {get; set;}
}
all you need is to tie the group to a different item in your model
#Html.RadioButtonFor(x => x.Field1, "Milk")
#Html.RadioButtonFor(x => x.Field1, "Butter")
#Html.RadioButtonFor(x => x.Field2, "Water")
#Html.RadioButtonFor(x => x.Field2, "Beer")
Ok here's how I fixed this
My model is a list of categories. Each category contains a list of its subcategories.
with this in mind, every time in the foreach loop, each RadioButton will have its category's ID (which is unique) as its name attribue.
And I also used Html.RadioButton instead of Html.RadioButtonFor.
Here's the final 'working' pseudo-code:
#foreach (var cat in Model.Categories)
{
//A piece of code & html here
#foreach (var item in cat.SubCategories)
{
#Html.RadioButton(item.CategoryID.ToString(), item.ID)
}
}
The result is:
<input name="127" type="radio" value="110">
Please note that I HAVE NOT put all these radio button groups inside a form. And I don't know if this solution will still work properly in a form.
Thanks to all of the people who helped me solve this ;)
I fixed a similar issue building a RadioButtonFor with pairs of text/value from a SelectList. I used a ViewBag to send the SelectList to the View, but you can use data from model too. My web application is a Blog and I have to build a RadioButton with some types of articles when he is writing a new post.
The code below was simplyfied.
List<SelectListItem> items = new List<SelectListItem>();
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("Texto", "1");
dictionary.Add("Foto", "2");
dictionary.Add("VĂ­deo", "3");
foreach (KeyValuePair<string, string> pair in objBLL.GetTiposPost())
{
items.Add(new SelectListItem() { Text = pair.Key, Value = pair.Value, Selected = false });
}
ViewBag.TiposPost = new SelectList(items, "Value", "Text");
In the View, I used a foreach to build a radiobutton.
<div class="form-group">
<div class="col-sm-10">
#foreach (var item in (SelectList)ViewBag.TiposPost)
{
#Html.RadioButtonFor(model => model.IDTipoPost, item.Value, false)
<label class="control-label">#item.Text</label>
}
</div>
</div>
Notice that I used RadioButtonFor in order to catch the option value selected by user, in the Controler, after submit the form. I also had to put the item.Text outside the RadioButtonFor in order to show the text options.
Hope it's useful!
I was able to use the name attribute that you described in your example for the loop I am working on and it worked, perhaps because I created unique ids? I'm still considering whether I should switch to an editor template instead as mentioned in the links in another answer.
#Html.RadioButtonFor(modelItem => item.Answers.AnswerYesNo, "true", new {Name = item.Description.QuestionId, id = string.Format("CBY{0}", item.Description.QuestionId), onclick = "setDescriptionVisibility(this)" }) Yes
#Html.RadioButtonFor(modelItem => item.Answers.AnswerYesNo, "false", new { Name = item.Description.QuestionId, id = string.Format("CBN{0}", item.Description.QuestionId), onclick = "setDescriptionVisibility(this)" } ) No
You can use Dictonary to map
Assume Milk,Butter,Chesse are group A (ListA)
Water,Beer,Wine are group B
Dictonary<string,List<string>>) dataMap;
dataMap.add("A",ListA);
dataMap.add("B",ListB);
At View , you can foreach Keys in dataMap and process your action

KendoUI MVC MultiSelect - in some cases only one return value

My KendoUI MultiSelect returns only the first selected value, if nothing was changed.
When I load the Viewpage, the MultiSelect is populated correctly with data and multiple selection. When I now proceed to save the data without having changed anything(!), the model transferred to my controller (most of the time, no pattern discernible) only contains the first selected item in the appropiate field. If I add new selections, only the first of the previously selected items is returned together with the freshly selected ones.
If I use .AutoBind(false) and then click once into the field and then outside again without having de-/selected anything, the model transferred to my controller contains the full contents on saving.
With multiple MultiSelects (on different data) on the same ViewPage, it is enough to use .AutoBind(false) on one MultiSelect (with the subsequent click like above), so that all MultiSelects suddenly return the full range of selected values.
Can anyone please explain this strange behaviour to me, possibly even with a fix?
Two different implementations in the View, which both show the same behaviour (GetDepotList() returns similar content to ViewData["DepotList"]):
#model List<LoadingUnitViewModel>
[...]
//Version 1
#(Html.Kendo().MultiSelectFor(m => m[i].FK_Depots)
.Name("[" + i.ToString() + "]." + "FK_Depots")
.Placeholder(Localize((string)ViewData["ControllerName"], "ChooseDepot"))
.DataValueField("Value")
.Value(Model[i].FK_Depots)
.DataTextField("Text")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetDepotList", (string)ViewData["ControllerName"]);
})
.ServerFiltering(true);
})
.HtmlAttributes(new { id = "_" + i + "__FK_Depots"})
)
//Version 2
#(Html.Kendo().MultiSelectFor(m => m[i].FK_Depots)
.Name("["+i.ToString()+"]."+"FK_Depots")
.Placeholder(Localize((string)ViewData["ControllerName"], "ChooseDepot"))
.BindTo(new MultiSelectList((System.Collections.IEnumerable)ViewData["DepotList"], "Id", "Name", Model[i].FK_Depots))
)
Edit:
As requested: The relevant parts of the LoadingUnitViewModel:
public class LoadingUnitViewModel
{
[Editable(false)]
[Display(Name = "Id")]
public int Id { get; set; }
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "FK_Depots")]
public List<string> FK_Depots { get; set; }
}
We've run into some weird issues with Kendo MultiSelect and not binding to the MVC Model, I'll show you what we've done. Part of the problem looks like is the name your giving your multiselect. I dnt know why you'd need to generate multiple names like you are doing. If you give it a single name attribute you can get all the values. This is what my multiselect looks like. Notice the Name is simply "Advertisers"
#(Html.Kendo().MultiSelect()
.Name("Advertisers")
.BindTo(Model.Advertisers)
.HtmlAttributes(new { style="width:445px;" })
.DataTextField("Text")
.Filter("startswith")
.MinLength(5)
.MaxSelectedItems(3)
.DataValueField("Value")
.AutoBind(false)
.Placeholder("Type the first five characters of the advertiser name...")
.Value(Model.Advertisers)
.HtmlAttributes(new {style = "width:415px;"})
.Events(e => e.Select("advertiserSelected"))
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetAccounts", "CrmIntegration");
})
.ServerFiltering(true);
})
)
On form submit inside your controller, the values weren't going to the model, so instead we pulled them like this.
string[] advertisers = Request.Form["Advertisers"].ToString().Split(',');
foreach (var s in advertisers)
{
// access to the values in multiselect
}
Edit
It should be noted that default model binder will look to bind the value to the property named in .Name("Property") regardless whether you use MultiSelectFor or not. I realized this after posting this answer.
#(Html.Kendo().MultiSelectFor(x => x.AdvId)
.Name("Advertisers")
//will bind to Advertisers, not AdvId on post

Form submission for mvc model enumerable using editor template

I have an editor template for my model view ViewSetup. My view to use template is simplified as
#model IEnumerable<ViewSetup>
#Html.EditorFor(s => s)
My ViewSetup editor template has form submission like below:
using (Ajax.BeginForm("Edit", new AjaxOptions() { HttpMethod = "Post" }))
{
#Html.HiddenFor(p => p.TradingPartner.ID)
<input type="submit" value="Save" />
}
So basically i need a form to be submitted for each element of the Enumerable. But I'm facing a problem on form submission. My controller to process post is:
public ActionResult Edit(ViewSetup formDataSent)
{
formDataSent.Save();
}
As per default model binding I'm getting null for TradingPartner property since the name in the html is :
<input name="[0].TradingPartner.ID" type="hidden" value="1"/>
What I need is to submit only the ViewSetup object on each element instead of an array. If I can get the index part in the name removed that could work for me. But I'm not sure how to get just the ViewSetup object on form submission.
I bet that if you base your editor on one item instead of a list of items then you would gain more flexibility.
#model IEnumerable<ViewSetup>
#foreach (var item in Model)
{
#Html.EditorFor(modelItem => item.TradingPartner)
}
I had the same issue, what resolved it for me was this:
#foreach (var item in Model)
{
#Html.EditorFor(modelItem => item.TradingPartner, null, "")
}
The third property of the EditorFor being blank will get rid of the "[0]" from your objects.