jQuery rocks.  It makes it really easy to do cool stuff client side with JavaScript, like these cascading DropDowns (aka Select Lists).  But at the end of the day, jQuery is JavaScript, and it can still be tricky to work with.  One of these days, Visual Studio will treat JavaScript like a first class language and life will be much easier.  You’ll see that there isn’t much JavaScript here, but it took me several iterations to get this right.

I’ll start with the UI code and JavaScript.  If you want, you can read further and see the Controller code and more.

In my View, I have two DropDowns (Select Lists) and some JavaScript:

<p>
    Country:
    <%= Html.DropDownList("Countries", ViewData["Countries"] 
             as List<SelectListItem>) %>
</p>
<p>
    Region:
    <%= Html.DropDownList("Regions", new List<SelectListItem>()) %>
</p>

<script src="../../Scripts/jquery-1.3.2.js"></script>

<script type="text/javascript">
    $(function() {
        var countries = $("#Countries");
        var regions = $("#Regions");
        countries.change(function() {
            regions.find('option').remove();
            $.getJSON('/Home/Regions', { countryId: countries.val() }, function(data) {
                $(data).each(function() {
                    $("<option value=" + this.RegionId + ">" + this.RegionName + "</option>").appendTo(regions);
                });
            });
        });
    });
</script>

The Countries DropDown is pre-populated with data passed in ViewData. 

The second DropDown, Regions gets loaded when Countries changes.

Here’s how the JavaScript and jQuery stuff works…

First I get the two Select Lists, using jQuery and the ID as the selector (jQuery makes it easy!):

        var countries = $("#Countries");
        var regions = $("#Regions");

Next I wire up the change event on the Countries list to an anonymous function (jQuery makes it easy!):

        countries.change(function() {
        // ...
            });

Inside that function I do a few things.  First, find all options in the Region Select List and remove them:

            regions.find('option').remove();

Next I need to get the data for the second Select List. Guess what?  jQuery makes this easy too.  Using the getJSON() method, I supply the Controller and Action, as well as the Id of the country so I can get the correct regions.  I also declare a function to call when the JSON result comes back:

        $.getJSON('/Home/Regions', { countryId: countries.val() }, function(data) {
        // ...
        });

The last thing to do is load all of the results into the Select List.  This actually was the hardest thing to do but in the end it is very little code.  I tried using jQuery to create options and adding them to the select list.  I tried creating a list of options and setting the html value of the Select list.  I tried a bunch of stuff.  But this worked great… Iterate over the results with jQuery’s .each function and call a function for each iteration.  The function creates some dynamic html and appends it to the Region list.  I must admit, I was surprised this worked.  I’d assume that appending would put it after the select list (<select id=”Regions” />) but it actually put it inside it!

        $(data).each(function() {
            $("<option value=" + this.RegionId + ">" + this.RegionName + "</option>").appendTo(regions);
        });

That’s it!  It works great.  Of course there are more complex ways to do this, with caching etc.  This is just one way to accomplish the goal.

Here’s some code from the Controller that helps make it work.  Just remember, this is demo code.  I’m not too worried about separation of concerns and similar details. In a production app, I’d be calling more services!

In the Index Action (in this sample the Select Lists are in my Index View) I load the countries list into ViewData.  Again, this is a demo and that may not be the best practice.

    public ActionResult Index()
    {
        ViewData["Countries"] = GetCountries();
        return View();
    }

    private List<SelectListItem> GetCountries()
    {
        List<SelectListItem> countries =
            _countryList.Select(c => new SelectListItem {Text = c.CountryName, Value = c.CountryId.ToString()}).ToList();
        countries.Insert(0, new SelectListItem{ Text="", Value = "0"});
        return countries;
    }

Then I have a Regions Action that returns the JSON result with the values to add to my Regions Select List:

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Regions(int countryId)
    {
        return Json(_regionList.Where(r => r.CountryId == countryId).Select(r =>
                                                                            new
                                                                                {
                                                                                    r.RegionName,
                                                                                    r.RegionId
                                                                                }));
    }

 

That covers most of it.  Happy coding.

Technorati Tags: , ,

4 thoughts on “Cascading Select Lists with MVC and jQuery

  1. In my case I have returned both RegionId and RegionName from the database. If you want to have access to RegionName and RegionId, you’ll need to bring both back from the database as I did (with the Json result).

    -Andy

    Reply
  2. I found that I could use the following in the anonymous callback: function(data) { $("#xyz").addItems(data); } instead of iterating using jQuery’s each iterator.

    What I haven’t figured/found out is how to show the RegionName in the Details view because the database has RegionId and that is what is available in the Deatils View.

    Reply

Leave a reply

required

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>