I've got an MVC 3 application with a page where users can request more information about our service.
When the user selects their state from a dropdown list, I want to use jQuery ajax to fetch a list of Products we offer in that state. For each Product, I'd like to display a check box next to the Product name. Then the user can select which Products they are interested in.
I'd like to bind the selected Products to a List property on the model for this view. So the model will have a property for name, email, etc. and a List property. Here's an example of the model class I'm trying to bind to:
public class RequestInformationModel
{
public string Name { get; set; }
public string Email { get; set; }
public List<Product> Products { get; set; }
}
Here is the controller method I will post the form to:
public ActionResult RequestInformation(RequestInformationModel model)
{
// do stuff
}
Any advice on how to accomplish this? Thanks very much.
If you prefix the name of the fields in your product object with the name of the list and their index in square brackets, then MVC will bind the list for you
E.g.
<input type="checkbox" name="Products[0].InterestedInCheckbox" />
<input type="checkbox" name="Products[1].InterestedInCheckbox" />
In this case the Product object needs a bool property called InterestedInCheckbox
You should have sequencial indexes starting at 0 for this method to work (you would need a hidden field and a slightly different technique if this is not the case)
Finally, I'd use the Html Checkbox helper for creating the checkboxes since this will output a hidden field after the checkboxes required:
(following code is untested)
#for (var i = 0; i < Model.Count; i++)
{
#Html.CheckBox(string.Format("Products[{0}]", i), Model[i])
}
I think what you want is something like this:
/* Handles ajax request */
[HttpPost]
public ContentResult(string selectedListBoxItem)
{
string content = "";
/* TODO: Query DB to get product suggestions based on selectedListBoxItem */
content = "<input type='checkbox' id='productId1' name='productSuggestion' value='productId1'>";
content += "<input type='checkbox' id='productId2' name='productSuggestion' value='productId2'>";
content += "<input type='checkbox' id='productId2' name='productSuggestion' value='productId2'>";
return Content(content);
}
/* Handles final submit */
[HttpPost]
public ActionResult HandleSubmit(string nameOfCheckboxCollection)
{
string[] arrayChecked = nameOfCheckboxCollection.Split(",");
foreach(string s in arrayChecked ) {
Model.addProduct(s);
}
}
Related
I am trying to find a good way to implement product filtering in my ASP.NET MVC application.
When a user selects a checkbox value, I want to send a key/value pair (or some other type of collection) to my controller where I can then lookup products in my DB that have the filter (like screen size) and value (say 50") and return that list.
Can someone please recommend a good approach for this? I would like to do this by passing URL parameters so our marketing folks can link to pre-filtered product listing pages.
I've been researching how to pass an object or collection through the URL but haven't had luck implementing it.
Here is a small example of what I'm trying to accomplish:
User selects "Screen Size - 50"
Pass key "size" and value "50" to MVC controller
public ActionResult subcategory(string brand, string cat, string cat2, Dictionary<string, string> filters)
Return list of products that have screen size
of 50", and have checkbox for "Screen Size - 50" checked in View.
Allow user to select multiple filters
Note that new products and associated filters (and their categories) will be added in the future. Ideally, the query string will consist of name/value pairs consisting of the category ID and the selected ID, for example
products/brand/NEC/Pro-Displays/LCD-Monitors?234=43&343=21
where 234 is the ID for the 'Screen Size' category and 43 is the ID for the '50"' monitor, and 343 is for the 'Brightness' category and 21 is the ID '450 Nit'
For reference, here is how I am rendering the checkbox filters in my View:
#for (int x = 0; x < Model.Count(); x++)
{
<div class="filter-cat" data-target="#list-#x">#Model.ElementAt(x).getName() <i class="fa fa-angle-down"></i></div>
<ul id="list-#x">
#for (int y = 0; y < Model.ElementAt(x).getFilterList().Count; y++)
{
<li>
<input type="checkbox" id="#Model.ElementAt(x).getFilterList().ElementAt(y).getDescription()" name="#Model.ElementAt(x).getFilterList().ElementAt(y).getDescription()" />
<label for="#Model.ElementAt(x).getFilterList().ElementAt(y).getDescription()">#Model.ElementAt(x).getFilterList().ElementAt(y).getDescription()</label>
</li>
}
</ul>
}
In order to bind back to your Dictionary<string, string> filters parameter, you will need to generate inputs in the format
<input type="checkbox" name="filters[xxx]" value="yyy" />
where xxx is the ID of the filter category, and yyy is the ID of the selected filter.
Note that since your submitting only the ID values (typeof int), then your parameter can be Dictionary<string, int> filters (the Key still needs to be string).
But since you have checkboxes meaning that a user can make multiple selections in a category (and dictionary keys must be unique), you will need a (say) suffix in the name attribute to make them unique.
The code in your view is ugly and difficult to read, debug and unit test, so I recommend you start with a view model(s) to represent what you want to display in the view
public class ProductFilterVM
{
public string Name { get; set; }
... // other properties relating to a product that you may want to display the view - e.g. Description
public IEnumerable<FilterCategoryVM> FilterCategories { get; set; }
}
public class FilterCategoryVM
{
public int ID { get; set; }
public string Name { get; set; }
public List<FilterSelectionVM> Selections { get; set; }
}
public class FilterSelectionVM
{
public int ID { get; set; }
public string Name { get; set; }
}
and in your GET method, initialize an instance of ProductVM and populate it based on the product being displayed, and return that to the view. The view then becomes
#model ProductFilterVM
....
<h2>#Model.Name</h2>
...
<h3>Filters</h3>
#using (Html.BeginForm("subcategory", "yourControllerName", FormMethod.Get))
{
foreach(var category in Model.FilterCategories)
{
int counter = 1; // used to generate unique keys
<h4>#category.Name</h4>
foreach (var selection in category.Selections)
{
string name = String.Format("filters[{0}-{1}]", category.ID, counter++);
<label>
<input type="checkbox" name="#name" value="#selection.ID" />
<span>#selection.Name</span>
</label>
}
}
<input type="submit" />
}
When you submit, the checked checkboxes will be bound to your filters parameter, and you will need to parse the Key back to an int after removing the suffix
public ActionResult subcategory(string brand, ..., Dictionary<string, string> filters)
{
foreach(KeyValuePair<string, int> item in filters)
{
int category = Convert.ToInt32(item.Key.Split('-')[0]);
int filter = item.Value;
}
}
I have a list of teams on my index page.
I'm trying to pass the text of an input(type text) from the index view back to the index controller, to reload the index page, this time only displaying items in my list which have matching text. eg - bob = bob
Index Controller
public ActionResult Index(string searchString)
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
var listOfTeams = from T in db.Teams
select T;
if (!String.IsNullOrEmpty(searchString))
{
listOfTeams = listOfTeams.Where(T => T.TeamName.Contains(searchString));
}
return View(listOfTeams.ToList());
}
How i'm trying to pass the data in the Index view
I've tried
<input type="text" id="inputTeamSearch" name="searchString" class="form-control" style="width:225px;height:60px" onblur="IsTextEmpty()" oninput="CheckTeams()" placeholder="Search">
#Html.ActionLink("Search", "Index")
and
#using(Html.BeginForm("Index", "Team"))
{
<input type="text" id="inputTeamSearch" name="searchString" class="form-control" style="width:225px;height:60px" onblur="IsTextEmpty()" oninput="CheckTeams()" placeholder="Search">
<input type="submit" id="Index" value="Index" />
Html.EndForm();
}
I'm sure this is probably a duplicate of some sort, if so please just pass me in the appropriate direction. I've looked for answers, but they're either long-winded or go into more complex detail than this.
So to post data to a controller you need a seperate post action which is decorated with the HttpPost attribute. This method needs to take a model as it's parameter:
[HttpPost]
Public ActionResult Index(IndexVM model)
{
var searchTerm = model.SearchTerm;
}
The view model needs to contain the fields that you intend to post.
Public class IndexVM
{
Public String SearchTerm { get; set; }
//Other model examples
public Int32 PageNumber { get; set; }
public Int32 NumOfItemsPerPage { get; set; }
}
Then your html needs to contain a text box that has the same name as the string property in your view model.
#Html.TextBoxFor(m => m.SearchTerm)
//And at the top of your html page you will need to include the model
#model Domain.Models.IndexVM
OR
<input type="text" name="SearchTerm">
Should work.
If you are already using an entity model you can create a new View model which contains the old entity and whatever else you need. So:
public class IndexVM
{
public Team Team { get; set; }
public String SearchTerm { get; set; }
}
Then on your index GET method where you're passing your team to your view you would have:
var view = new IndexVM();
view.Team = //your team object
return View(view);
I have a view model in my ASP.NET MVC app that holds a List of objects. Each of these objects are visualized using EditorTemplate. Using a dropdown I can add dynamically new object to the List on postback. The problem is when I try to remove a specific element from the list.
public class MyViewModel
{
public List<MyModel> Items { get; set; }
public MyViewModel()
{
this.Items = new List<MyModel>();
}
}
public class MyModel
{
public int Id { get; set; }
public string Text { get; set; } // I bind this to UI using EditorFor html helper
}
HomeController:
[HttpPost]
public ActionResult Index(MyViewModel myViewModel)
{
myViewModels.Items.Add(new MyModel()); // Simulate initializing the Id and Text properties
return View(myViewModel);
}
Then in Index.cshtml:
if (Model.Items != null)
{
#Html.EditorFor(m => Model.Items); // EditorTemplate is used here...
}
So in my template file, called Item.cshtml I have:
#model MVCApp.Models.MyModel
<div>
#Html.HiddenFor(m => m.Id)
#Html.EditorFor(m => m.Text)
<span class="deleteItem" data-item-id="#Model.Id">Delete</span>
</div>
Now I don't know how to process the deletion of the specific item when I click on "Delete". My goal is to have the item deleted from the collection in the controller ('Items' list property of the view model). I can delete the div element using jQuery but the property would not be removed from the 'Items' collection and I would like to avoid jQuery to preserve data integrity between server and client side. I tried to call another action in HomeController called Remove(int? id) where the id parameter is the Id property of 'MyModel', but I need to pass 'MyViewModel' object in Remove action in order to access the 'Items' list, remove the element and then redirect to 'Index' action with the changed collection. How can I achieve that? Thanks!
Update
If you are looking to do it using. You can submit form using submit button and pass in the id to remove and then calling controller action and code accordingly.
I have the view that contains the checkbox and Submit button as shown below.
#using (Html.BeginForm())
{
<fieldset>
<legend style="font-size: 100%; font-weight: normal">Delete</legend>
<p> Are you sure you want to delete?</p>
#foreach (string resource in resources)
{
if (resource != "")
{
<input type="checkbox" name="Resources" title="#resource" value="#resource" checked="checked"/>#resource
<br />
}
}
<br />
#Html.HiddenFor(m => m.AttendeeListString)
#Html.HiddenFor(m => m.ResourceListString)
<span class="desc-text">
<input type="submit" value="Yes" id="btnYes" />
</span>
<span class="desc-text">
<input type="submit" value="No" id="btnNo" />
</span>
</fieldset>
}
Below is the Controller code...
public ActionResult DeleteResource(RoomModel roomModel)
{
...
}
RoomModel contains some other data...
Now how can i access the checkbox value in controller?
Note : I have lot more information that need to be send to Controller when i clicked on submit button... Can anybody suggest some solution....
Answer :
I have added these two property to My model
public List<SelectListItem> Resources
{
get;
set;
}
public string[] **SelectedResource**
{
get;
set;
}
And My view check box i have updated as follows
#foreach (var item in Model.Resources)
{
<input type="checkbox" name="**SelectedResource**" title="#item.Text" value="#item.Value" checked="checked"/>#item.Text
<br /><br />
}
And in Controller ...
if (roomModel.SelectedResource != null)
{
foreach (string room in roomModel.**SelectedResource**)
{
resourceList.Add(room);
}
}
Note: The name of check box and Property in the model should be same. In my case it is SelectedResource
You have a few options. The easiest would be:
1) Parameter bind a view model with the Resources property. I recommend this way because it's the preferred MVC paradigm, and you can just add properties for any additional fields you need to capture (and can take advantage of validation easily by just adding attributes).
Define a new view model:
public class MyViewModel
{
public MyViewModel()
{
Resources = new List<string>();
}
public List<string> Resources { get; set; }
// add properties for any additional fields you want to display and capture
}
Create the action in your controller:
public ActionResult Submit(MyViewModel model)
{
if (ModelState.IsValid)
{
// model.Resources will contain selected values
}
return View();
}
2) Parameter bind a list of strings named resources directly in the action:
public ActionResult Submit(List<string> resources)
{
// resources will contain selected values
return View();
}
It's important to note that in the question, the view is creating checkboxes that will send the string value of all checked resources, not boolean values (as you might expect if you used the #Html.CheckBox helper) indicating if each item is checked or not. That's perfectly fine, I'm just pointing out why my answer differs.
In MVC action, have a parameter that corresponds to the name of the checkbox, something like:
bool resources
bool[] resources
use javascript or jquery to collect all the value and post to the controller
var valuesToSend='';
$('input:checked').each(function(){
valuesToSend+=$(this).val() + "$";//assuming you are passing number or replace with your logic.
});
and after submit call ajax function
$.ajax({
url:'yourController/Action',
data:valuesTosend,
dataType:'json',
success:function(data){//dosomething with returndata}
})
or else you can pass the model to controller. if you implemented Model -View-ViewModel pattern.
public class yourViewModel
{
public string Id { get; set; }
public bool Checked { get; set; }
}
Action methods
[HttpPost]
public ActionResult Index(IEnumerable<yourViewModel> items)
{
if(ModelState.IsValid)
{
//do with items. (model is passed to the action, when you submit)
}
}
I'm assuming that the resources variable is generated in the Controller or can be placed onto the ViewModel. If so, then this is how I would approach it:
Your view model would have a Resources dictionary added to it, and would look something like this:
public class RoomModel
{
public Dictionary<string,bool> Resources { get; set; }
// other values...
}
You populate the Resources Dictionary with the names of your resource items as the key (string) and set the "checked" value (bool) to a default state of false.
e.g. (in your [HttpGet] controller)
// assuming that `resource` is your original string list of resources
string [] resource = GetResources();
model.Resources = new Dictionary<string, bool>();
foreach(string resource in resources)
{
model.Resources.Add(resource, false);
}
To render in the view, do this:
#foreach (string key in Model.Resources.Keys)
{
<li>
#Html.CheckBoxFor(r => r.Resources[key])
#Html.LabelFor(r => r.Resources[key], key)
</li>
}
This will then enable the [HttpPost] controller to automatically populate the dictionary onto the Model when you post back:
public ActionResult DeleteResource(RoomModel roomModel)
{
// process checkbox values
foreach(var checkbox in roomModel.Resources)
{
// grab values
string resource = checkbox.Key;
bool isResourceChecked = checkbox.Value;
//process values...
if(isResourceChecked)
{
// delete the resource
}
// do other things...
}
}
I have added these two property to My model
public List<SelectListItem> Resources
{
get;
set;
}
public string[] **SelectedResource**
{
get;
set;
}
And My view check box i have updated as follows
#foreach (var item in Model.Resources)
{
<input type="checkbox" name="**SelectedResource**" title="#item.Text" value="#item.Value" checked="checked"/>#item.Text
<br /><br />
}
And in Controller ...
if (roomModel.SelectedResource != null)
{
foreach (string room in roomModel.**SelectedResource**)
{
resourceList.Add(room);
}
}
Note: The name of check box and Property in the model should be same. In my case it is SelectedResource
I have a simple form that I would like to validate on form submission. Note I have stripped out the html for ease of viewing
<%=Html.TextBox("LastName", "")%> //Lastname entry
<%=Html.ValidationMessage("LastName")%>
<%=Html.TextBox("FirstName", "")%>//Firstname entry
<%=Html.ValidationMessage("FirstName")%>
<%=Html.DropDownList("JobRole", Model.JobRoleList)%> //Dropdownlist of job roles
<% foreach (var record in Model.Courses) // Checkboxes of different courses for user to select
{ %>
<li><label><input type="checkbox" name="Courses" value="<%=record.CourseName%>" /><%= record.CourseName%></label></li>
<% } %>
On submission of this form I would like to check that both FirstName and LastName are populated (i.e. non-zero length).
In my controller I have:
public ActionResult Submit(string FirstName, string LastName)
{
if (FirstName.Trim().Length == 0)
ModelState.AddModelError("FirstName", "You must enter a first name");
if (LastName.Trim().Length == 0)
ModelState.AddModelError("LastName", "You must enter a first name");
if (ModelState.IsValid)
{
//Update database + redirect to action
}
return View(); //If ModelState not valid, return to View and show error messages
}
Unfortunately, this code logic produces an error that states that no objects are found for JobRole and Courses.
If I remove the dropdownlist and checkboxes then all works fine.
The issue appears to be that when I return the View the view is expecting objects for the dropwdownlist and checkboxes (which is sensible as that is what is in my View code)
How can I overcome this problem?
Things I have considered:
In my controller I could create a JobRoleList object and Course object to pass to the View so that it has the objects to render. The issue with this is that it will overwrite any dropdownlist / checkbox selections that the user has already made.
In the parameters of my controller method Submit I could aslo capture the JobRoleList object and Course object to pass back to the View. Again, not sure this would capture any items the user has already selected.
I have done much googling and reading but I cannot find a good answer. When I look at examples in books or online (e.g. Nerddinner) all the validation examples involve simple forms with TextBox inputs and don't seems to show instances with multiple checkboxes and dropdownlists.
Have I missed something obvious here? What would be best practice in this situation?
Thanks
The best practice would be to accept a view-specific model. You could have a shared model that has both the properties that are needed to render the page and the properties required on post or separate models for rendering and accepting the post parameters. I usually go with a shared model as that means I can simply return the model I've received, suitably re-populated with any data needed to generate menus (such as JobList and Courses). Often I will have a method that takes a model of this type and returns a view with the menu properties populated.
public ActionResult JobsView( JobsViewModel model )
{
model.JobList = db.Jobs.Select( j => new SelectListItem
{
Text = j.Name,
Value = j.ID.ToString()
});
...
return View( model );
}
Then call this method from any of my actions that require a view with this type of model, to ensure that the model has the proper menu data.
// on error, return the view
return JobsView( model );
When using a model, you can also use DataAnnotations to decorate your model properties and let the model binder do validation for you, as well as enable client-side validation based on the model. Any validation not supported by the existing attributes can be implemented by creating your own attributes or done within the controller action.
public class JobsViewModel
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int JobRole { get; set; }
[ScaffoldColumn(false)]
public IEnumerable<SelectListItem> JobRoleList { get; set; }
...
}
public ActionResult Submit( JobsViewModel model )
{
if (ModelState.IsValid)
{
... convert model to entity and save to DB...
}
return JobsView( model );
}
Then enable validation in your HTML
...
<script type="text/javascript" src="<%= Url.Content( "~/scripts/MicrosoftMvcValidation.js" ) %>"></script>
<% Html.EnableClientValidation(); %>
... begin form...
<%=Html.TextBoxFor( m=> m.LastName )%>
<%=Html.ValidationMessageFor( m => m.LastName )%>
<%=Html.TextBoxFor( m=> m.FirstName )%>
<%=Html.ValidationMessageFor( m => m.FirstName )%>
<%=Html.DropDownListFor( m=> m.JobRole, Model.JobRoleList)%>
<% foreach (var record in Model.Courses) // Checkboxes of different courses for user to select
{ %>
<li><label><input type="checkbox" name="Courses" value="<%=record.CourseName%>" /><%= record.CourseName%></label></li>
<% } %>
In simple MVC validation your supposed to pass the strongly typed object in other words your views model.
Example:
public ActionResult Update(Employees employee)
{
if (employee.Name.Trim().Length == 0)
ModelState.AddModelError("Name", "Name is required.");
// ETC....
}