Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cascading drop-downs in MVC 3 Razor view

I am interested in how to implement cascading dropdown lists for addresses in a Razor view. My Site entity has a SuburbId property. Suburb has a CityId, and City has ProvinceId. I would like to display dropdowns for all of Suburb, City, and Province on the Site view, where e.g. the suburb dropdown will initially display "First select a City", and the City dropdown, "First select a province". On selecting a province, cities in the province are populated etc.

How can I achieve this? Where do I start?

like image 886
ProfK Avatar asked Dec 16 '10 09:12

ProfK


People also ask

What are cascading dropdowns?

A cascading drop-down list is a series of dependent DropDownList controls in which one DropDownList control depends on the parent or previous DropDownList controls. The items in the DropDownList control are populated based on an item that is selected by the user from another DropDownList control.


2 Answers

Thanks Darin for your lead to the solution. It greatly helped me to arrive to the point. But as 'xxviktor' mentioned, I did got circular ref. error. To get rid of it, I've done this way.

    public string GetCounties(int countryID)     {         List<County> objCounties = new List<County>();         var objResp = _mastRepo.GetCounties(countryID, ref objCounties);         var objRetC = from c in objCounties                       select new SelectListItem                       {                           Text = c.Name,                           Value = c.ID.ToString()                       };         return new JavaScriptSerializer().Serialize(objRetC);     } 

And to achieve auto cascading, I've slightly extended jQuery extension this way.

        $('#ddlCountry').cascade({             url: '@Url.Action("GetCounties")',             paramName: 'countryID',             childSelect: $('#ddlState'),             childCascade: true         }); 

And the actual JS is using this parameter as below (inside JSON request).

                // trigger child change                 if (opts.childCascade) {                     opts.childSelect.change();                 } 

Hope this helps someone with similar issue.

like image 43
Kris Avatar answered Sep 21 '22 15:09

Kris


Let's illustrate with an example. As always start with a model:

public class MyViewModel {     public string SelectedProvinceId { get; set; }     public string SelectedCityId { get; set; }     public string SelectedSuburbId { get; set; }     public IEnumerable<Province> Provinces { get; set; } }  public class Province {     public string Id { get; set; }     public string Name { get; set; } } 

Next a controller:

public class HomeController : Controller {     public ActionResult Index()     {         var model = new MyViewModel         {             // TODO: Fetch those from your repository             Provinces = Enumerable.Range(1, 10).Select(x => new Province             {                 Id = (x + 1).ToString(),                 Name = "Province " + x             })         };         return View(model);     }      public ActionResult Suburbs(int cityId)     {         // TODO: Fetch the suburbs from your repository based on the cityId         var suburbs = Enumerable.Range(1, 5).Select(x => new         {             Id = x,             Name = "suburb " + x         });         return Json(suburbs, JsonRequestBehavior.AllowGet);     }      public ActionResult Cities(int provinceId)     {         // TODO: Fetch the cities from your repository based on the provinceId         var cities = Enumerable.Range(1, 5).Select(x => new         {             Id = x,             Name = "city " + x         });         return Json(cities, JsonRequestBehavior.AllowGet);     } } 

And finally a view:

@model SomeNs.Models.MyViewModel  @{     ViewBag.Title = "Home Page"; }  <script type="text/javascript" src="/scripts/jquery-1.4.4.js"></script> <script type="text/javascript">     $(function () {         $('#SelectedProvinceId').change(function () {             var selectedProvinceId = $(this).val();             $.getJSON('@Url.Action("Cities")', { provinceId: selectedProvinceId }, function (cities) {                 var citiesSelect = $('#SelectedCityId');                 citiesSelect.empty();                 $.each(cities, function (index, city) {                     citiesSelect.append(                         $('<option/>')                             .attr('value', city.Id)                             .text(city.Name)                     );                 });             });         });          $('#SelectedCityId').change(function () {             var selectedCityId = $(this).val();             $.getJSON('@Url.Action("Suburbs")', { cityId: selectedCityId }, function (suburbs) {                 var suburbsSelect = $('#SelectedSuburbId');                 suburbsSelect.empty();                 $.each(suburbs, function (index, suburb) {                     suburbsSelect.append(                         $('<option/>')                             .attr('value', suburb.Id)                             .text(suburb.Name)                     );                 });             });         });     }); </script>  <div>     Province:      @Html.DropDownListFor(x => x.SelectedProvinceId, new SelectList(Model.Provinces, "Id", "Name")) </div> <div>     City:      @Html.DropDownListFor(x => x.SelectedCityId, Enumerable.Empty<SelectListItem>()) </div> <div>     Suburb:      @Html.DropDownListFor(x => x.SelectedSuburbId, Enumerable.Empty<SelectListItem>()) </div> 

As an improvement the javascript code could be shortened by writing a jquery plugin to avoid duplicating some parts.


UPDATE:

And talking about a plugin you could have something among the lines:

(function ($) {     $.fn.cascade = function (options) {         var defaults = { };         var opts = $.extend(defaults, options);          return this.each(function () {             $(this).change(function () {                 var selectedValue = $(this).val();                 var params = { };                 params[opts.paramName] = selectedValue;                 $.getJSON(opts.url, params, function (items) {                     opts.childSelect.empty();                     $.each(items, function (index, item) {                         opts.childSelect.append(                             $('<option/>')                                 .attr('value', item.Id)                                 .text(item.Name)                         );                     });                 });             });         });     }; })(jQuery); 

And then simply wire it up:

$(function () {     $('#SelectedProvinceId').cascade({         url: '@Url.Action("Cities")',         paramName: 'provinceId',         childSelect: $('#SelectedCityId')     });      $('#SelectedCityId').cascade({         url: '@Url.Action("Suburbs")',         paramName: 'cityId',         childSelect: $('#SelectedSuburbId')     }); }); 
like image 164
Darin Dimitrov Avatar answered Sep 21 '22 15:09

Darin Dimitrov