Everyone recognizes the problem with dropdownlists which are dependant on eachother. We select something from dropdownlist A and then dropdownlist B can be filled with items based on your selection in A. Simple to build with an automatic postback and event handlers on the controls, but nobody likes the page refresh. So time to get our hands on jQuery and JSON.

Let’s suppose we have an application page in which we have two dropdownlist:
<wssawc:DVDropDownList ID=”dropdownlistA” runat=”server”></wssawc:DVDropDownList>
<wssawc:DVDropDownList ID=”dropdownlistB” runat=”server”></wssawc:DVDropDownList>

With a simple jQuery script, we can hook up the two dropdownlists, so that a change in dropdownlistA triggers the loading of items in dropdownlistB without refreshing the page. We use the jQuery selector, select[id$='dropdownlistA'], because we know that the above tag is rendered by SharePoint as a select control. The ID however is prepended with a unique part, so we let jQuery search for select controls where the ID ends on our ID. Always use the complete ID you specify in the design file to avoid multiple results in jQuery select statements.

<script type=“text/javascript”>
  $(document).ready(function() {
    $(“select[id$='dropdownlistA']“).change(function() {
      //clear previous items from dropdownlist
      $(“select[id$='dropdownlistB']“).html(“”);
      //select value from master dropdownlist
       var ddlASelectedValue = $(“select[id$='dropdownlistA'] > option[selected]“).attr(“value”);
       if (ddlASelectedValue != ”) {
        //if mastervalue not empty, select items for slave dropdownlist
        $.getJSON(‘ValuesDdlB.ashx?SelectedValue=’ + ddlASelectedValue, function(valuesB) {
          $.each(valuesB, function() {
          //add every item to slave dropdownlist
          $(“select[id$='dropdownlistB']“).append(
            $(“<option></option>”).val(this['ID']).html(this['Name']));
          });
        });
      }
    });
  });
</script>

We have to create an .ashx file (Generic Handler), which is called by jQuery like in the script above (getJSON call). Right click the project in Visual Studio and select ‘Add’, ‘New item …’. If we have the option to uncheck ‘Place code in a separate file’, we choose the Generic Handler as template. Otherwise take a Text File or Code File and override the extension to ashx. In SharePoint we can only use the ashx files without a separate code file behind. First add a @WebHandler directive and a class definition that implements the IHttpHandler interface. Next we have to implement the IsReusable and the ProcessRequest method. To reference external dll’s, we can use the @Assembly directive.

Imports System.Web
Imports System.Web.Services

Public Class ValuesDllB
    Implements System.Web.IHttpHandler

    Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
      Dim pattern As String = “”"Name”":”"{0}”",”"ID”":”"{1}”"”
      Dim sbValues As StringBuilder = New StringBuilder()

      Dim values() As String
      
If (context.Request.QueryString(“SelectedValue”) = “MasterValue1″) Then
        values = New String() {“subvalue1mastervalue1″, “subvalue2mastervalue1″}
      Else
        values = New String() {“subvalue1mastervalue2″, “subvalue2mastervalue2″}
      End If

      Dim i As Integer = 0
      For Each value As String In values
        sbValues.Append(“{“ & String.Format(pattern, value, i) & “},”)
        i += 1
      Next

      context.Response.ContentType = “application/json”
      context.Response.ContentEncoding = Encoding.UTF8
      context.Response.Write(“[" + sbValues.ToString().Substring(0, sbValues.Length - 1) + "]“)
      context.Response.End()
    End Sub

    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
      Get
        Return False
      End Get
    End Property
End Class

The above file has to be deployed in the \LAYOUTS\ folder, like we would do with custom application pages. Try to use a subfolder to avoid file name conflicts across multiple solutions.

Ready we are, time to test it!

Update 20 august 2009: Since most advantages also have disadvantages, I have to tell you that there is also one here. The second dropdownlist is provisioned by jQuery and you will notice that in code, the SelectedIndex, SelectedItem and SelectedValue are not set. So time for some other jQuery. First add a textbox under the dropdownlists and a div tag with display style set to none (to hide it).

<div style=”display:none;”>
  <wssawc:InputFormTextBox ID=”selectedValueDdlB” runat=”server”>
  </wssawc:InputFormTextBox>
</div>

Next add the jQuery script to catch a change in dropdownlist B and set the textbox field.

$(“select[id$='dropdownlistB']“).change(function() {
  var pid = $(“select[id$='dropdownlistB'] > option[selected]“).attr(“value”);
  if (pid != ) {
    $(“input[id$='selectedValueDdlB']“).val(pid);
  }
});

In code you can read out the value of this textbox.