Filtering Dropdowns in a ASP.Net MVC App using jQuery and jSon.

Thursday, March 11, 2010

I have often come across the need to load a dropdown list based on the selection of another drop down list on a form.  In web forms it is really easy, especially if you were just doing a regular page post back.  You simply set the dropdown’s AutoPostBack property to true and then on the change event load the second dropdown based on the value of the first event.

Then along came the Ajax Toolkit that had Ajax server controls that would basically do that for you pretty simply without a full page postback.  You could see a demo of that functionality here.

How to do this with MVC

Now in MVC all the cool server controls are gone but with just a little extra work we can get the same functionality pretty easily.  Moreover, using the Json protocol makes the population of the second drop down much lighter accross the wire.

In my example, I have a pretend scenario where the user selects an event from the first drop down and based on the value of the first selection, the options of the second is dropdown is populated.

Here is the view code I am using for this example. (Note: I am using the Sparks View Engine for the View Framework instead of the Microsoft MVC View Framework because it makes the mark up much cleaner.

   1: <viewdata EventList="SelectList" />
   2:  
   3:  
   4: <content name="head">
   5:     <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min-vsdoc.js" type="text/javascript"></script>
   6:     <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.min.js" type="text/javascript"></script>
   7: </content>
   8:  
   9:  
  10:  
  11:  
  12: #using (Html.BeginForm())
  13: #        {
  14:           
  15:  <div>
  16:             <fieldset>
  17:                 <legend>Event Information</legend>
  18:                 <p>
  19:                     <label for="eventname">Event name:</label>
  20:                     ${Html.DropDownList("eventname", EventList)}
  21:                 </p>
  22:                 <p>
  23:                     <label for="eventdate">Event Date:</label>
  24:                     <select id="eventdate">
  25:                         <option value="">Select Date:</option>
  26:                     </select>
  27:                 </p>
  28:                 <p>
  29:                     <input type="submit" value="See Event Info" />
  30:                 </p>
  31:             </fieldset>
  32:         </div>         
  33:           
  34:           
  35: #}

 

If you are wondering why my second selection list is not generated by the HtmlHelper class it is only because I was not able to append to it using jQuery.  Since it really wasn’t needed anyway I just decided not to bother.

The name of my view is called Events and it resides in my Home controller and the code for populates that view looks like this.

   1: public ActionResult Events()
   2:         {
   3:             IList<EventDto> events = new List<EventDto>();
   4:             const int upper = 5;
   5:             for (int i = 0; i < upper; i++)
   6:             {
   7:                 var eventItem = new EventDto() {EventCode = i.ToString(), Eventname = string.Format("Name {0}", i)};
   8:                 events.Add(eventItem);
   9:             }
  10:  
  11:             var eventList = new SelectList(events, "EventCode", "EventName");
  12:             ViewData["EventList"] = eventList;
  13:             return View();
  14:         }

 

Nothing special here.  I am just creating a list of events and then passing it to a SelectList so it can be populated in the view.

So when the page loads the HTML looks like this:

   1:  
   2:                 <legend>Event Information</legend>
   3:                 <p>
   4:                     <label for="eventname">Event name:</label>
   5:                     <select id="eventname" name="eventname"><option value="0">Name 0</option>
   6: <option value="1">Name 1</option>
   7: <option value="2">Name 2</option>
   8: <option value="3">Name 3</option>
   9: <option value="4">Name 4</option>
  10: </select>
  11:                 </p>
  12:                 <p>
  13:                     <label for="eventdate">Event Date:</label>
  14:                     <select id="eventdate">
  15:                         <option>Select Date:</option>
  16:                     </select>
  17:                 </p>
  18:                 <p>
  19:                     <input value="See Event Info" type="submit">
  20:                 </p>
  21:             

And the page looks like this:

page1

The JsonResult Function

ASP.Net MVC comes with different controller actions, one of them being the JsonResult action.  Like the ActionResult, the JsonResult sends data back to the view but in this case, the response is converted into JSON.

   1: [AcceptVerbs(HttpVerbs.Get)]
   2:         public JsonResult EventDates(string eventCode)
   3:         {
   4:             IList<EventDateDto> eventDates = BuildEventDates(eventCode);
   5:             return Json(eventDates);
   6:         }

In writing the function, first I have to tell it action to respond do.  In this case I want it to respond to “GET” requests, so I need to add the AcceptVerbs attribute to the function with the “GET” verb stated.  The function needs to have the parameter for the event code that the user selected in the dropdown, and then based on the parameter the application gets a list of dates.  These collection dates (are formatted as strings in this case) are then passed back by calling the Json() function.

 

The jQuery function:

So in order to get the data for the second dropdown we need to make an AJAX call to the server and pass it the value of the first dropdown when its “change” event is fired.

When the event is fired, we can then use the jQuery $.AJAX call to make a “GET” request to the URL as such:  …/Home/EventDates?eventCode=3.  When you look at the header of the request you also notice that “Accept” attribute is set to “application/json, text/javascript, */*”.

   1: <script type="text/javascript" charset="utf-8">
   2:         $(function(){
   3:           $("select#eventname").change(function(){
   4:                 var data = $(this).val();
   5:                 var json = {eventCode: data};
   6:                 
   7:                 
   8:                 $.ajax({
   9:                   type: "GET",
  10:                   url: "/Home/EventDates",
  11:                   data: json,
  12:                   dataType: "json",
  13:                   error: function(xhr, status, error) {
  14:                     alert("error routine");
  15:                   },
  16:                   success: function(res){
  17:                     var $dropdown = $("select#eventdate");
  18:                     $dropdown.find('option').remove().end();
  19:                     $dropdown.append('<option value="">Select Date</option>');
  20:                     for (var i = 0; i < res.length; i++) {
  21:                         $("select#eventdate").append('<option value="' + res[i].EventDateName + '">' + res[i].EventDateName + '</option>');
  22:                       }
  23:                       
  24:                   }
  25:                 });
  26:           });
  27:         })
  28:         </script>

Notice that the data we are passing must in the request must be in a jSon format.  So in the case the data we are passing is {eventCode:3}.  The response we get back is:

[{"EventDateCode":"10/27/2009","EventDateName":"10/27/2009"},{"EventDateCode":"10/30/2009","EventDateName":"10/30/2009"},
{"EventDateCode":"11/2/2009","EventDateName":"11/2/2009"},{"EventDateCode":"11/5/2009","EventDateName":"11/5/2009"},
{"EventDateCode":"11/8/2009","EventDateName":"11/8/2009"},{"EventDateCode":"11/11/2009","EventDateName":"11/11/2009"},
{"EventDateCode":"11/14/2009","EventDateName":"11/14/2009"},{"EventDateCode":"11/17/2009","EventDateName":"11/17/2009"},
{"EventDateCode":"11/20/2009","EventDateName":"11/20/2009"}]

 

If the response is successful, the callback function takes the collection that is returned and build a string of <option> tags and append it to the select object.

page1after

Update:

In the MVC 2 version the JsonResult response will by default throw an exception. This is because of a subtle vulnerability in which someone could gain access to sensitive information. You can get the details from Phil Haack's post. So the moral of the story is if you passing sensitive imformation in the response, it is probably better for you to make a POST rather than doing GET like I did in this example. If you are not passing sensitive information and you still would to use a GET request then you will need to change the JSon method from:

return JSon(eventDates);

to

return JSon(eventDates, JSonRequestBehavior.AllowGet);

comments powered by Disqus