I am using the latest version of jQuery and ASP.NET MVC 3 with the Razor view engine.
I have tried Google looking for a decent example of loading a child drop down list when a parent drop down item is selected. I am looking to do this via jQuery
AJAX
using JSON
. My knowledge of this is zero.
I have a Category class with a list of 开发者_Python百科categories. It's a parent-child association.
If I select a category from the parent drop down list, then all the child categories need to be listed in the child drop down list for the selected parent category.
This is what I currently have, but need to complete it, not sure if I am in the right direction:
$(document).ready(function () {
$('#ddlParentCategories').change(function () {
alert('changed');
});
});
I loaded my drop down list from my view model as such:
@Html.DropDownListFor(x => x.ParentCategoryId, new SelectList(Model.ParentCategories, "Id", "Name", Model.ParentCategoryId), "-- Select --", new { id = "ddlParentCategories" })
The first item has text "-- Select --" (for both parent and child drop down lists). On initial page load nothing must be loaded in the child drop down list. When a value is selected then the child drop down list must be populated. And when "-- Select --" is selected again in the parent drop down list then all the items in the child drop down list must cleared except "-- Select --".
If possible, if the child categories is loading, how do I display that "round" loading icon?
UPDATE
I have updated my code to Darin's code, and I cannot get it to work properly:
Category
class:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string MetaKeywords { get; set; }
public string MetaDescription { get; set; }
public bool IsActive { get; set; }
public int? ParentCategoryId { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}
EditProductViewModel
class:
public class EditProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
public bool IsActive { get; set; }
public string PageTitle { get; set; }
public bool OverridePageTitle { get; set; }
public string MetaKeywords { get; set; }
public string MetaDescription { get; set; }
public int ParentCategoryId { get; set; }
public IEnumerable<Category> ParentCategories { get; set; }
public int ChildCategoryId { get; set; }
public IEnumerable<Category> ChildCategories { get; set; }
}
ProductController
class:
public ActionResult Create()
{
EditProductViewModel viewModel = new EditProductViewModel
{
ParentCategories = categoryService.GetParentCategories()
.Where(x => x.IsActive)
.OrderBy(x => x.Name),
ChildCategories = Enumerable.Empty<Category>(),
IsActive = true
};
return View(viewModel);
}
public ActionResult AjaxBindingChildCategories(int parentCategoryId)
{
IEnumerable<Category> childCategoryList = categoryService.GetChildCategoriesByParentCategoryId(parentCategoryId);
return Json(childCategoryList, JsonRequestBehavior.AllowGet);
}
Create
view:
<tr>
<td><label>Parent Category:</label> <span class="red">*</span></td>
<td>@Html.DropDownListFor(x => x.ParentCategoryId,
new SelectList(Model.ParentCategories, "Id", "Name", Model.ParentCategoryId),
"-- Select --",
new { data_url = Url.Action("AjaxBindingChildCategories"), id = "ParentCategories" }
)
@Html.ValidationMessageFor(x => x.ParentCategoryId)
</td>
</tr>
<tr>
<td><label>Child Category:</label> <span class="red">*</span></td>
<td>@Html.DropDownListFor(x => x.ChildCategoryId,
new SelectList(Model.ChildCategories, "Id", "Name", Model.ChildCategoryId),
"-- Select --",
new { id = "ChildCategories" }
)
@Html.ValidationMessageFor(x => x.ChildCategoryId)
</td>
</tr>
<script type="text/javascript">
$(document).ready(function () {
$('#ParentCategories').change(function () {
var url = $(this).data('url');
var data = { parentCategoryId: $(this).val() };
$.getJSON(url, data, function (childCategories) {
var childCategoriesDdl = $('#ChildCategories');
childCategoriesDdl.empty();
$.each(childCategories, function (index, childCategory) {
childCategoriesDdl.append($('<option/>', {
value: childCategory, text: childCategory
}));
});
});
});
});
</script>
It goes into my AjaxBindingChildCategories action and it brings back records, it just doesn't want to display my child category dropdownlist. I had a look in Fire Bug and the error that I get is:
GET AjaxBindingChildCategories?parentCategoryId=1
500 Internal Server Error
Here's an example of cascading drop down lists. As always start by defining a view model:
public class MyViewModel
{
[DisplayName("Country")]
[Required]
public string CountryCode { get; set; }
public IEnumerable<SelectListItem> Countries { get; set; }
public string City { get; set; }
public IEnumerable<SelectListItem> Cities { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// TODO: Fetch countries from somewhere
Countries = new[]
{
new SelectListItem { Value = "FR", Text = "France" },
new SelectListItem { Value = "US", Text = "USA" },
},
// initially we set the cities ddl to empty
Cities = Enumerable.Empty<SelectListItem>()
};
return View(model);
}
public ActionResult Cities(string countryCode)
{
// TODO: based on the selected country return the cities:
var cities = new[]
{
"Paris", "Marseille", "Lyon"
};
return Json(cities, JsonRequestBehavior.AllowGet);
}
}
a view:
@model MyViewModel
@using (Html.BeginForm())
{
<div>
@Html.LabelFor(x => x.CountryCode)
@Html.DropDownListFor(
x => x.CountryCode,
Model.Countries,
"-- Select country --",
new { data_url = Url.Action("cities") }
)
@Html.ValidationMessageFor(x => x.CountryCode)
</div>
<div>
@Html.LabelFor(x => x.City)
@Html.DropDownListFor(
x => x.City,
Model.Cities,
"-- Select city --"
)
@Html.ValidationMessageFor(x => x.City)
</div>
<p><input type="submit" value="OK" /></p>
}
and finally the unobtrusive javascript in a separate file:
$(function () {
$('#CountryCode').change(function () {
var url = $(this).data('url');
var data = { countryCode: $(this).val() };
$.getJSON(url, data, function (cities) {
var citiesDdl = $('#City');
citiesDdl.empty();
$.each(cities, function (index, city) {
citiesDdl.append($('<option/>', {
value: city,
text: city
}));
});
});
});
});
jQuery script will look like this:
<script type="text/javascript">
function getCities(abbr) {
$.ajax({
url: "@Url.Action("Cities", "Locations")",
data: {abbreviation: abbr},
dataType: "json",
type: "POST",
error: function() {
alert("An error occurred.");
},
success: function(data) {
var items = "";
$.each(data, function(i, item) {
items += "<option value=\"" + item.Value + "\">" + item.Text + "</option>";
});
$("#City").html(items);
}
});
}
$(document).ready(function(){
$("#State").change(function() {
var abbr = $("#State").val();
getCities(abbr);
});
});
</script>
A repository to retrieve data could look like this (obviously hook it up to live data though):
public class LocationRepository : ILocationRepository
{
public IQueryable<State> GetStates()
{
return new List<State>
{
new State { Abbreviation = "NE", Name = "Nebraska" },
new State { Abbreviation = "NC", Name = "North Carolina" }
}.AsQueryable();
}
public IQueryable<City> GetCities(string abbreviation)
{
var cities = new List<City>();
if (abbreviation == "NE")
{
cities.AddRange(new List<City> {
new City { Id = 1, Name = "Omaha" },
new City { Id = 2, Name = "Lincoln" }
});
}
else if (abbreviation == "NC")
{
cities.AddRange(new List<City> {
new City { Id = 3, Name = "Charlotte" },
new City { Id = 4, Name = "Raleigh" }
});
}
return cities.AsQueryable();
}
}
public interface ILocationRepository
{
IQueryable<State> GetStates();
IQueryable<City> GetCities(string abbreviation);
}
Controllers could look like this:
public class LocationsController : Controller
{
private ILocationRepository locationRepository = new LocationRepository();
[HttpPost]
public ActionResult States()
{
var states = locationRepository.GetStates();
return Json(new SelectList(state, "Id", "Name"));
}
[HttpPost]
public ActionResult Cities(string abbreviation)
{
var cities = locationRepository.GetCities(abbreviation);
return Json(new SelectList(cities, "Abbreviation", "Name"));
}
}
I am assuming you populate the parent dropdown from server side and also the first option in this parent dd is "--- Select ---"
You can try this
$(document).ready(function () {
var $parent = $('#ddlParentCategories');
var $child = $('#ddlChildCategories');
$child.find("option:gt(0)").remove();
if(!$parent.children().eq(0).is(":selected")){
$parent.change(function () {
$.ajax({
url: "urlToFetchTheChild",
data: { categoryId: this.value },
success: function(data){
//The data you send should be a well formed array of json object containing code/value pair of child categories
for(var i = 0;i<data.length;i++){
$child.append("<option value='"+ data[i].code +"'>"+ data[i].value +"</option>");
}
}
});
});
}
});
精彩评论