I have many DropDownLists on page
class BigViewModel
{
public List<SmallViewModel> SmallVM {get;set;}
public List<SelectListItem> Items {get;set;}
//some other properties
}
class SmallViewModel
{
public string ItemId {get;set;}
//some other properties
}
<table>
#for( var i = 0;i<Model.SmallVM.Count();i++)
{
<tr>
<td>
#Html.DropdownListFor(m=> m.SmallVM.ItemId, Model.Items)
</td>
</tr>
}
//display other properties
</table>
in controller
bigViewModel.Items = List<SelectListItem>
{
new SelectListItem{Value = "1", Text = "aaa"},
new SelectListItem{Value = "2", Text = "bbb"},
new SelectListItem{Value = "3", Text = "ccc"},
}
bigViewModel.SmallVM = new List<SmallViewModel>
{
new SmallViewModel{ItemId = 3},
new SmallViewModel{ItemId = 2},
}
In controller I set diffrent ItemId for every SmallVM and each DropDownList uses the same Items collection. I want to set default Value from SmallViewModel for each DropDownList. For example in this case there are two DropDownLists first should display default text "ccc" and second "bbb".
Should I put diffrent List<SelectedListItem> for every SmallViewModel and set them Selected property or there is other way?
This behavior has been reported as a bug on CodePlex but not yet fixed. Using DropDownListFor() in a for loop does not bind correctly and the first option is always selected despite the value of the property. In order for DropDownListFor() to work correctly when using a collection, you need to use an EditorTemplate for the model.
In /Views/Shared/EditorTemplates/SmallViewModel.cshtml
#model SmallViewModel
#Html.DropdownListFor(m => m.ItemId, (SelectList)ViewData["Items"])
Then in the main view
#model BigViewModel
#using(Html.BeginForm())
{
// Pass the select list to the EditorTemplate as addtionalViewData
#Html.EditorFor(m => m.SmallVM, new { Items = Model.Items })
<input type="submit" />
}
You should now have 2 <select> controls displaying "ccc" and "bbb" respectively.
Based on your code updates, I think you just need to modify your view code to:
#Html.DropdownListFor(m=> m.SmallVM[i].ItemId, Model.Items)
However, I have distinct feeling that this is very much an XY problem, and although this change will make everything wire up on post, you're still not going to be getting what you actually need for the true problem you're trying to solve.
Related
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>
Trying to populate a DropDownList depending on logged in windows user's username. However, the list should have only one item if the condition fails.
Problem: The custom item is not added to the list when condition meets. Looked at lot of posts on SO but with no luck. Any direction would help.
Controller:
public ActionResult Location()
{
UserPermissionDataContext dbContext = new UserPermissionDataContext();
var viewModel = new UserDetail();
var locationList = new SelectList(
(
from t in dbContext.UserDetails
where t.username.Equals(System.Environment.UserName)
select new SelectListItem{Text = t.location, Value = t.level}
), "Value", "Text");
if (locationList.Select(i => i.Value).Contains("Global"))
ViewData["Locations"] = locationList;
else
ViewData["Locations"] = new SelectList(locationList, "Value", "No Access");
return View(viewModel);
}
View:
<tr>
<td>Global</td>
<td>
<div>
#Html.DropDownList("location", (SelectList)ViewData["Locations"])
</div>
</td>
</tr>
What would be the steps to generate a list in that case when condition failes with one item only which would have the item as "No Access".
You can use this
if (locationList.Any(i => i.Value.Contains("Global")))
In my MVC application I have a view where I will display different data from a SQL table. This will generate different amount of drop down lists and text boxes, depending on what is passed in from the Model.
My issue is if I want to then use that data I can't seem to figure out how I can relate control X to object Y in SQL. For example, if I have 2 textboxes that I want to do an update on, then when the Post happens in my application the FormCollection parameter will let me see the Value of the objects, but not their control name or any form of identifying factor.
I could set the Value to a combination of the entered value + a name, then split this, but it seems very much like a lazy workaround.
I've tried to assign an ID to each, for example:
#foreach (DataObject item in Model.AllDataObjects)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Data)
</td>
<td>
#if (item.Rule.Contains("Yes;No"))
{
#Html.DropDownListFor(model => item.Value, new List<SelectListItem>
{
new SelectListItem {Text="Yes", Value="Yes"},
new SelectListItem {Text="No", Value="No" }
}, new { #id = item.ObjectId });
}
else
{
#Html.TextAreaFor(model => item.Value, new { style = "width: 400px;", #rows = 5, #id = item.ObjectId })
}
</td>
</tr>
}
Edit: The following is my Post ActionResult method in the Controller, albeit it isn't complete as I can't figure out how to get an ID for the control from the FormCollection
[HttpPost]
[ValidateInput(false)]
public ActionResult UpdateData(FormCollection collection, int objectId=0)
{
try
{
int propertyTypeId = 0;
string propertyValue = string.Empty;
// Get all the control values from the collection
string[] allValues = new string[] { };
IValueProvider valueProvider = collection.ToValueProvider();
foreach(string key in collection.Keys)
{
ValueProviderResult result = valueProvider.GetValue(key);
allValues = result.RawValue as string[];
}
ObjectData objectData = _execution.GetObjectDetails(0);
UpdateDataResponse result = _execution.UpdateData(0, objectId,
objectValue, UserName);
return RedirectToAction("Details",
new { ObjectId = objectData.ObjectId, error = result.ErrorMessage });
}
catch (Exception ex)
{
// My exception handling here
}
}
So I can see in the mark-up that the controls are assigned the object ID as their own ID, but how can I get this back? When I check FormCollection I only see the values for each control, but no way of identifying which is which.
Edit: I'm using MVC version 4.
A form only submits the values of its successful controls (as name/value pairs based on the controls name and value attributes) so if you do not generate a control for the ObjectId properties, they will not be submitted.
However, you current use of foreach loop will not allow you to obtain any meaning information from the data which is posted because all your names are identical and there is no way to reliable match up which value belongs to which item in the collection. Instead use a for loop or EditorTemplate in the view and bind to your model, rather than using FormCollection.
The view should be
#for (int i = 0; i < Model.AllDataObjects.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m.AllDataObjects[i].Name)</td>
<td>#Html.DisplayFor(m => m.AllDataObjects[i].Data)</td>
<td>
#Html.HiddenFor(m => m.AllDataObjects[i].ObjectId)
#if (Model.AllDataObjects[i].Rule.Contains("Yes;No"))
{
#Html.DropDownListFor(m => m.AllDataObjects[i].Value, new SelectList(new string[]{ "Yes", "No" }));
}
else
{
#Html.TextAreaFor(m => m.AllDataObjects[i].Value, new { style = "width: 400px;", #rows = 5 })
}
</td>
</tr>
}
And assuming the model in the view is #model MyModel, change the POST method to
[HttpPost]
[ValidateInput(false)]
public ActionResult UpdateData(MyModel model)
and the value of model.AllDataObjects will contain a collection with its ObjectId and Value properties correctly bound.
For more information on why using a foreach loop will not work, refer to this answer.
Here is what my view looks like:
#model Affiliate
<div class="box paint color_16">
<div class="title">
<h4><i class="icon-tasks"></i><span>#Model.CompanyName's Commissions</span> </h4>
</div>
<div class="content top ">
<div class="subtitle">
#Html.ActionLink("Void", "DeleteInvoice", new { commList = "??", affId = Model.Id }, new { #class = "btn" })
#Html.ActionLink("Create Invoice", "CreateInvoice", new { commList = "??", affId = Model.Id }, new { #class = "btn" })
#Html.ActionLink("Pay", "PayInvoice", new { commList = "??", affId = Model.Id }, new { #class = "btn" })
</div>
<table class="table table-striped table-hover">
<tr>
<h3>Commissions</h3>
</tr>
<tr>
<td></td>
<td>Amount</td>
<td>Status</td>
<td>Action</td>
</tr>
#foreach (var item in Model.Commissions)
{
<tr>
#if (item.Status == ViewBag.PaymentStatus || ViewBag.PaymentStatus == "All")
{
<td>#Html.CheckBox("commId", new { value = item.Id })</td>
<td>#Html.DisplayFor(x => item.PayoutAmount)</td>
<td>#Html.DisplayFor(x => item.Status)</td>
}
</tr>
}
</table>
</div>
What I want to be able to do is when I hit an actionlink on the top, grab all the items from the table that are checked, and pass that list of id's to the controller logic. I am assuming a viewmodel may be the solution, something like this:
public Affiliate affilite { get; set; }
public List<int> selectedItems { get; set; }
etc.
But how to I get the selected Items into that VM selectedItems container?
Based off your comments, you don't seem to be looking for the most "correct" answer, but rather just a quick and dirty "how would I do this" answer. If you just want to pass the list, you could setup your controller action like this:
public ActionResult MyAction(int[] id)
{
...
}
Or, you seem to indicate it is strongly typed to a view model with a property that contains a List (I would shorten the name of the property, you'll see why in a second).
In javascript, the easiest thing to do would be to use jQuery to bind a click event on your hyperlink that gets the list of items that are checked and appends that to the query string.
$("#myLink").click(function()
{
var url = "site.com/action?";
var ids = $(".table").find("input:checked");
ids.each(function()
{
url += "id=" + $(this).val() + "&"
});
window.location = url;
});
Basically, you want to create one long query string with the action parameter's name repeated over and over, which identifies an array. It looks something like this (id is for int[] id in MyAction):
id=15&id=20&id=25&id=30&....
And then once the query string is built, redirect the user to that url. MVC should then be able to bind that to an array and you're all set.
That's basically the idea, anyway; the syntax and the javascript I wrote could be way off so don't copy my code and expect it to work as is - I wrote that off the top of my head. If your action is bound to a viewmodel, then you need to set the parameter in the query string to the name of the property of your model:
selectedids=1&selectedids=2&selectedids=3...
Or, if the array is a property of an object, which is a property of the model...
model.selectedids=1&model.selectedids=2&model.selectedids=3...
You'll just need to play around with it some.
Use html checks inside form tag ( you could use helpers too) and post the model to a post action.
MVC will serialize the model automatically
I almost have this solved but need a little push.
Here's what I have:
In the database I have a field called active that is a bit field (True/False)
I have placed a dropdownlist on the View form like this:
<%= Html.DropDownList("lstActive", new SelectList((IEnumerable)ViewData["ActiveList"])) %>
In my controller, I simply have this code to generate the True/False in the dropdown:
List<string> activeList = new List<string>();
activeList.Add("True");
activeList.Add("False");
ViewData["ActiveList"] = new SelectList(activeList);
I want to bind to the field in the database called active and select it in the dropdown. When I view it like this I get this:
alt text http://rjmueller.net/sitesimages/temp/dropdown.gif
So the questions are these:
Obviously I am not pointing to the Value and Text property but what is that in this case?
And how do I select the value that is in the database?
Any help would be appreciated.
First, this is probably better suited to radio buttons, not a select. Second, you really ought to have a view model with a property that is an IEnumerable<SelectListItem> that supplies the values for the select. You can construct that directly in the model.
var model = new ViewModel();
model.ActiveList = new List<SelectListItem>
{
new SelectListItem { Text = "Yes", Value = "true" },
new SelectListITem { Text = "No", Value = "false" }
};
model.Active = false; // this will be the default
return View( model );
Then in your view (strongly-typed to your view model type):
<%= Html.DropDownListFor( m => m.Active, Model.ActiveList ) %>
Using radio buttons, you can omit the list (since there are only the two choices).
<%= Html.RadioButtonFor( m => m.Active, true ) %> Yes
<%= Html.RadioButtonFor( m => m.Active, false ) %> No
Here's a couple of suggestions for you.
First, your DropdownList's name is "lstActive", so if you create a List<SelectListItem> called "lstActive" and pass that back in ViewData, you don't have to do anything fancy with boxing. Then your declaration looks like:
<%= Html.DropDownList("lstActive") %>
easy, huh?
In your controller, you create your List. Here's a method I've used:
private List<SelectListItem> GetAccounts(User user)
{
var items = new List<SelectListItem>();
foreach (Account account in user.Accounts)
{
var item = new SelectListItem();
item.Text = account.Name;
item.Value = account.AccountId.ToString();
if (ActiveAccount == account.AccountId)
item.Selected = true;
items.Add(item);
}
return items;
}
Basically, what I'm trying to point out is that you can set a property on your SelectListItem that you wish to be displayed as selected. Here, I'm using my own code for Users and Accounts, but you'd substitute your own data based on your db query.
First thing, you're recreating a SelectList the ViewData data, you should declare the DropBox as follows:
<%= Html.DropDownList("lstActive", ViewData["ActiveList"]) %>
Second, instead of creating a generic list on the controller, create a SelectList and add SelectListItems to it:
var activeList = new SelectList
{
new SelectListItem { Text = "True", Value = true },
new SelectListItem { Text = "False", Value = false }
};
ViewData["ActiveList"] = activeList;
This should clarify:
Drop-down Lists and ASP.NET MVC
For each select list element you need to set the Text and Value properties...
One solution could be as follows:
Model:
public class NameValue
{
public string Name { get; set; }
public string Value { get; set; }
}
Controller:
string currentActiveValue = myDB.active.ToString();
List<NameValue> yesNoList = new List<NameValue>
{
new NameValue { Name = "Yes", Value = "True" },
new NameValue { Name = "No", Value = "False" }
};
SelectList myActiveList = new SelectList(yesNoList, "Name", "Value", currentActiveValue);
ViewData["ActiveList"] = myActiveList;
View:
div>Is Active: <%= Html.DropDownList("ActiveList") %></div>