Thursday, May 28, 2009

Cascading Drop Down with ASP.NET MVC, jQuery with JSON

Cascading drop down is webform is pretty easy by using postback - or UpdatePanel. In MVC, how would it be done? jQuery comes to the rescue for this problem. Since ASP.NET MVC is able to return a JSON format result, what we need to do then is just calling that Controller's Action via jQuery and then display the drop down. Let's use an example:Say I want to list available counties per state. So the the first drop down will be the state drop down, and based on the selection of the state, the second drop down will display available counties for the selected state.

Here is a snippet of the HTML for our two drop downs, for both states and counties.
 
<%= Html.DropDownList("StateID", Model.StateList) %>
<select name="CountyID" id="CountyID"></select>
So since we need to (re)populate the county drop down for each selection/change of the StateID drop down, we use the .change method in jQuery.
 
$(document).ready(function() {
    $("#StateID").change(function() {    });
});
First, we need to get the selected State and also make sure that the default State selection will also execute and display the list of Counties appropriately.
 
$(document).ready(function() {
    $("#StateID").change(function() {
        var strStateID = "";
        strStateID = $(this)[0].value; // get the selected state id
    })
    .change(); // making sure the event runs on initialization for default value
});
Once we have the state id, we can then call our controller's action to get a list of counties back via AJAX.
 
$(document).ready(function() {
    $("#StateID").change(function() {
        var strStateID = "";
        strStateID = $(this)[0].value; // get the selected state id
        var url = "/MyController/Counties/" + strStateID;
        // call controller's action
        $.getJSON(url, null, function(data) {
            // do something once the data is retrieved
        });
    })
    .change(); // making sure the event runs on initialization for default value
});
With the data in hand, we can (re)build the County drop down with the values returned from the controller.
 
$(document).ready(function() {
    $("#StateID").change(function() {
        var strStateID = "";
        strStateID = $(this)[0].value; // get the selected state id
        var url = "/MyController/Counties/" + strStateID;
        // call controller's action
        $.getJSON(url, null, function(data) {
            // do something once the data is retrieved
            $("#CountyID").empty();
            $.each(data, function(index, optionData) {
                $("#CountyID").append("<option value='"
                     + optionData.CountyID
                     + "'>" + optionData.CountyName
                     + "</option>");
            });
        });
    })
    .change(); // making sure the event runs on initialization for default value});

10 comments:

Anonymous said...

Sounds great and very clean, but it doesn't work. I am surely doing something wrong.
Can you show the controller code?

Joe said...

Sure, here is the controller "MyController" code that returns the County list within a given State:

public JsonResult Counties(int stateid)
{

var counties = (from c in db.Counties
where (c.StateId == stateid)
order by c.CountyName
select new { countyid = c.CountyId, CountyName = c.CountyName });

return this.Json(counties);

}

Anonymous said...

It works! Thank you very much. Your code is very clean!

Damien said...

thank you much for this post, the addition of .change() at the end of the function fixed one of the problems i was having for a long time with this type of code.

unfortunately, now i am having another problem.

i have a dropdown B that gets populated when i change dropdown A. i then have dropdown C that populates based on changes to B.

if i change A, C isn't being populated correctly based on the changes made to B due to the dropdown items updating.

C updates properly if i make another change to B after changing A.

do you have any idea what's causing this? it seems like the change event for B isn't being fired when i change A, but i'm not sure how to fix that.

Joe said...

Damien, you will need to fire the change event in drop down B once there is a change in A. Since a change in A has its own handler code which then populate B, trigger the change event on B manually after the population of B.

So something like this:

$.getJSON(url, null,function(data) {
// populating drop down B

// trigger change event on B
$("#CountyID").change();
}

PhilG said...

Thanks for this, spent a while grappling with it.

For me the controller needs a tweak to work

return this.Json(counties, JsonRequestBehavior.AllowGet);

Joe said...

Thanks, Phil. The post was written using MVC 1.0, but you are correct, in MVC2 it needs to be: return this.Json(counties, JsonRequestBehavior.AllowGet);

Anonymous said...

Great!

I have a problem, though :(

When populating with default values (via the bottom .change() call) the StateId works well, the CountyId drop down is properly populated, but it is not selected, leaving the drop down with first entry instead of the default value one.

I assume this is because its content is filled upon the getJSON and there is no place where the 'selected=selected' attribute is added.

So I've added such a line right after the .change(); like this:

$("#CountyId option[value=AAA]").attr('selected', 'selected');

where AAA is the default value.

This does not work.

HOWEVER, if I put an alert(); before that line, the alert is popping up, and when I close the dialog box, the selected value DOES
work.

What could be a reason? It's as if the DOM is not reflecting the getJSON results only till after a certain event occurs (the alert() in this case).

Any ideas?

Thanks,
Edmund

Golois said...

Thank you, it was very very very helpful. Thx for sharing

Anonymous said...

Hi Author,

Your markup does not have a form tag, does this mean that you are using GET?

Because I cannot get my controller to get the value of the dropdownlist, because my dropdownlists are inside a POST form.