I’ve blogged previously that I’m on a mission to build samples of implementing all the ASP.NET Ajax Control Toolkit Features WITHOUT the Ajax Control Toolkit.

If you’re ever used the Cascading Drop Down control extender in the Ajax Control Toolkit, then you know that it’s syntactically easy to use but a real pain to debug.

You may have also discovered that any JavaScript code that effects the state of an ASP.NET Drop Down Control has it’s influence lost when you submit your form back to the server.

This is due to a collection of tomfoolery that happens under the covers with the Drop Down control as it pertains to ASP.NET View State.

To side step the ViewState complexities, we’ll do all the Cascading Drop Down UI on the client and only use ASP.NET Server controls the minimal amount necessary to easily post our selected values back to the server.

Here is the UI interaction flow that we want.

This is the way that the page renders.

Then the user selects from the list of car Makes.

Note that at this point, ONLY the Car “Makes” list is populated.

We can’t display the models until we know what “Make” the user is interested in.

As a result, the “Models” and “Colors” controls are not even active yet.

After we select a car “Make”, the “Models” Drop Down has been filled with values as the result of an AJAX call to the server that passes the “Make” value as an argument so that the related “Models” can be returned.

The same happens when we select a “Model”.

The colors that the model is available in now appear in the “Colors” Drop Down.

When the user has selected a value in each of the three text boxes and clicks the Submit Button, the ASP.NET’ page’s Code-Behind Retreives the selected values and displays a result message.

There is, however, a but of trickery necessary to make this happen.

First, the Drop-Dwn lists are NOT ASP.NET Server Controls, they are simple HTML <select> elements.

This first one (Makes) is populated with statically defined values but you could populate them from a service call when the page loads if you felt it neccessary.

New car companies don’t come into existance very often, so I don’t think it’s necessary in this case.


    <div id='SelectionContainer' runat='server'>
    <select id='DDListMake' name='DDListMake' style='width: 200px'>
		<option>Select...</option>
		<option value='Ford'>Ford</option>
		<option value='Cadilac'>Cadilac</option>
		<option value='Toyota'>Toyota</option>
		<option value='Jeep'>Jeep</option>
		<option value='Mazda'>Mazda</option>
	</select>
    <asp:HiddenField ID='TextMake' runat='server' ClientIDMode='Static' />
    <br />
    <select id='DDListModels' style='width: 200px' disabled='true'></select>
    <asp:HiddenField ID='TextModel' runat='server' ClientIDMode='Static' />
    <br />
    <select id='DDListColors' style='width: 200px' disabled='true'></select>
    <asp:HiddenField ID='TextColor' runat='server' ClientIDMode='Static' />
    <br /><br />
    <asp:Button ID='ButtonSend' runat='server' Text='Submit Selections'
                onclick='ButtonSend_Click' />
    </div>
    <asp:Label ID='LabelResult' runat='server' Text=''></asp:Label>

Note that the Drop Down Lists are simple HTML Select Elements.

The Button and the Label are ASP.NET controls.

Take special note of the three “Hidden” ASP.NET contol instances.

We will populate these with the values that the user selects in each Drop Down so that we can retrieve those values in our code-behind.

Implementing the simplest possible web service.

The .NET platform is rife with ways to serve up content via HTTP.

We have ASMX, ASPX, ASHX, WCF, MVC Views, all of which could be used to serve non markup data.

In our case, we will use a simple .cshtml page to return JSON formatted data.


@{
var data = '';

switch (Request.QueryString[0])
{
  case 'Make':
    switch (Request.QueryString[1])
    {
      case 'Ford':
        data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Mustang\'},' +
                '{\'value\': \'Fusion\'}, {\'value\': \'Taurus\'},' +
               '{\'value\': \'Focus\'},{\'value\': \'Fiesta\'}]';
        break;
        case 'Cadilac':
            data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Escalade\'},
                                                  {\'value\': \'CTS\'}]';
            break;
        case 'Toyota':
            data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Mustang\'},
                                                  {\'value\': \'Escort\'}]';
            break;
        case 'Jeep':
            data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Limited\'},
                                                  {\'value\': \'CJ\'},' +
                    '{\'value\': \'Cherokee\'}, {\'value\': \'Liberty\'}]';
            break;
        case 'Mazda':
            data = '[{\'value\': \'Choose ...\'}, {\'value\': \'RX9\'},
                                                  {\'value\': \'CX9\'}]';
            break;
        default:
            data = '[{\'value\': \'Error ...\'}]';
            break;

    }
    break;

    case 'Model':
      switch (Request.QueryString[1])
      {
        case 'Ford-Mustang':
        case 'Cadilac-Escalade':
        case 'Jeep-Liberty':
          data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Red\'},
                                                {\'value\': \'Blue\'}]';
          break;
        case 'Ford-Fusion':
        case 'Cadilac-CTS':
        case 'Mazda-RX7':
          data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Pink\'},
                                                {\'value\': \'Yellow\'}]';
          break;
        case 'Ford-Taurus':
        case 'Jeep-Limited':
           data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Black\'},
                                                 {\'value\': \'Brown\'}]';
           break;
        case 'Ford-Focus':
        case 'Jeep-Cherokee':
           data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Green\'},
                                                 {\'value\': \'Fucia\'}]';
           break;
        case 'Ford-Fiesta':
        case 'Jeep-CJ':
        case 'Mazda-CX9':
           data = '[{\'value\': \'Choose ...\'}, {\'value\': \'Yellow\'},
                                                 {\'value\': \'White\'}]';
           break;
        default:
           data = '[{\'value\': \'Error ...\'}]';
           break;

        }
        break;
    }
    Response.Write(data);
}

The logic here is pretty simple.

We inspect Request.QueryString[0] which will contain an argument that indicates which of our Drop-Down lists caused the Aax Service Call.

Request.QueryString[1] will contain the VALUE of the control indicated in Request.QueryString[0] – that is to say the value of the control that the user just interacted with.

Based on those two parameters our service will return a jQuery data set containing one of two things.

  • A list of Models for the Make the user selected.
  • A list of Colors that the selected “Make” and “Model” is available in.

Since we’ll be using jQuery we’ll need to include jQuery in our page.

I always do this in the MasterPage since I use jQuery all over my web site.


 <script src='Scripts/jquery-1.4.1.min.js' type='text/javascript'></script>

Having done that we can wire up event handlers for when the user makes a selection in each of the Drop-Down lists.

The page’s jQuery code looks like this.


<script type='text/javascript'>
   $(function ()
   {
       $('#DDListMake').change(function ()
       {
          var selectedvalue = $(this).val();
          $('#TextMake').val(selectedvalue);
          $('#DDListModels')[0].options.length = 0;
          $('#DDListColors')[0].options.length = 0;
          $.getJSON('Service.cshtml?op=Make&amp;Make=' + 
		            $('#DDListMake').val(),
          function (data)
          {
             $.each(data, function (key, item)
             {
                $('#DDListModels').append($('<option></option>')
                                  .attr('value', item.value)
                                  .text(item.value));
             });
          });
          $('#DDListModels').removeAttr('disabled');
          $('#DDListColors').attr('disabled', true);
       });

       $('#DDListModels').change(function () {
          var selectedvalue = $(this).val();
          $('#TextModel').val(selectedvalue);
          $('#DDListColors')[0].options.length = 0;
          $.getJSON('Service.cshtml?op=Model&amp;Model=' + 
		            $('#DDListMake').val() + '-' +
                    $('#DDListModels').val(), function (data)
          {
             $.each(data, function (key, item)
             {
                $('#DDListColors').append($('<option></option>')
                                  .attr('value', item.value)
								  .text(item.value));
             });
             $('#DDListColors').removeAttr('disabled');
          });
        });

     $('#DDListColors').change(function () {

         var selectedvalue = $(this).val();
         $('#TextColor').val(selectedvalue);
     });

   });
</script>

For each of the three Drop-Down lists we wire up a “change” event handler.

The first one looks like this.


$('#DDListMake').change(function ()

Based on whichever Drop-Down is being addressed we empty the appropriate subseqent Drop-Downs.


$('#DDListModels')[0].options.length = 0;
$('#DDListColors')[0].options.length = 0;

And store the selected value in the Hidden ASP.NET control.


var selectedvalue = $(this).val();
$('#TextMake').val(selectedvalue);

As an additional nice behavior, we set the appropriate Drop-Downs as enabled or disabled - as appropriate.


$('#DDListModels').removeAttr('disabled');
$('#DDListColors').attr('disabled', true);

The logic cascades with each selection so that the changed event handler for the “Makes” list effects both the “Models” and “Colors” lists.

The “Models” change event handler only has to be concerned with the state of the “Colors” Drop-Down.

When the submit button is clicked the ASP.NET form is submitted. (You might want to use the enabled / disabled technique above to disable the Submit Button until all the drop-downs have been used to select values so that you don’t get “null” value errors if the user submits the form prematurely.)

Note that all of our Drop-Downs live inside a container


<div id='SelectionContainer' runat='server'>

Note also that the container is set to runtat=server.

This is done so that we can hide all the selection markup after the user submits the page.

Our .aspx Code-Behind loosk like this.


public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}
protected void ButtonSend_Click(object sender, EventArgs e)
{
SelectionContainer.Visible = false;
LabelResult.Text = "You selected a " + TextColor.Value + " "
+ TextMake.Value + " "
+ TextModel.Value;

}
}

Until you get a feel for the jQuery syntax (and understand the View-State issues with the DropDown control) this might look a bit complex, but once you’re using it it’s prety simple to “re-use” the model anywhere you need a Cascading Drop Down feature in your web site.

It’s especially light weight and this method is easily portable to WebMatrix or ASP.NET MVC.

[ Download the demo code HERE ]