Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Inside CascadingDropDown

4.08/5 (6 votes)
5 Dec 2007CPOL9 min read 1  
This article may help you to understand how the CascadingDropDown works and how to work with it.

Introduction

CascadingDropDownExtender is one of the most popular AjaxControlToolkit extenders. But, because there is a help article about the server-side part of the control and almost nothing about the client-side (that really makes it work), developers became baffled when they needed to add some advanced event handling or customization.

I think that if you understand how it works, it is much easier to implement your own functionality. So, in this article, I'll describe how the CascadingDropDown works, and then I'll try to answer some common questions.

Little server

To render it shortly for those who do not know - CascadingDropDown – is an extender control used to fill the data in one DropDownList depending on the value chosen in another DropDownList. It gets all the necessary data through AJAX using a web method. Now, let’s consider its organization in more details.

So, the CascadingDropDown is an extender for DropDownList. As the target control, we set the "dependent" (or child) DropDownList (i.e., the one that is going to be filled). To set the ID of the parent control, we use the ParentControlID property, which, by the way, is not required (i.e., there can be no parent DropDownList). Other required properties (in addition to TargetControlID) are Category and ServiceMethod. The ServiceMethod contains the name of the web method used by CascadingDropDown, and Category fulfills the role of filter name and will be passed to the web method as part of one of the parameters. The optional properties are:

  • PromptText (text to display before the user has selected a value from the DropDownList),
  • PromptValue (value set when PromptText is displayed),
  • EmptyText (text to display when the DropDownList has no data to display),
  • EmptyValue (value set when EmptyText is displayed),
  • LoadingText (text which is displayed during the loading),
  • ServicePath (the path to web-service; if you do not declare it, the PageMethod will be used instead),
  • ContextKey (place for storing any additional information),
  • UseContextKey (tells you to pass or not to pass ContextKey as one of the properties to the web-method),
  • SelectedValue (default value for the child DropDownList).

Apart from the above described properties, the CascadingDropDown has a couple of helper static methods which can be used in web methods, but they will be discussed later. All that the CascadingDropDown does on the server-side is collect the values of the above-described properties into the script descriptor and «sends» it to the related client class that is derived from Sys.UI.Behavior (exactly – from AjaxControlToolkit.BehaviorBase). Then, all the main process runs on the client-side:

Big client

Image 1

In the initialize method, override the handlers subscribed on the change event of the child and parent (if defined) select DOM elements. Also, if the parent select is specified, there appears a kind of bond between the parent and child selects: the child select receives the CascadingDropDownParentControlID field that refers to the parent select, and the parent select gets (if it already does not have it) an array field called childDropDown, to which a reference to the child select is added. Besides all these, during the initialization stage, all options are removed from the child select DOM element, the CascadingDropDownCategory field (which will contain the value of the Category property) is added to it, the text from PromptText or LoadingText properties is put in the parent select, and some other minor actions are performed. Also, if the child select has no parent, or the parent is specified and in the moment initialize is called, its selected value isn’t equal to PromptValue or EmptyValue, the child select will be filled by data (i.e., there will be the same action as when the user changes the value of the parent select). So, it’s high time to look how the data filling occurs.

Everything begins in the _onParentChange method (notice the underline prefix - this method isn't for public usage). As it was already said, the web method passes 2 or 3 parameters depending on the UseContextKey value. These arguments are knownCategoryValues, category, and contextKey. All these arguments are strings. It is logical that _category and _contextKey field values are passed as second and third parameters. The value for the first one is more interesting. It is formatted as follows: if the child select has no parent, the value is null. If the parent select is specified, then its CascadingDropDownCategory field value and selected value are added to knownCategoryValues. If the parent select has the CascadingDropDownParentControlID field specified, then the parent of the parent select is taken and the above described actions are repeated, and this continues to the end of the hierarchy. New pairs are added to the beginning of knownCategoryValues, and, as a result, knownCategoryValues will look like this:

'category1:value1;category2:value2;...'

Here, I would like to take your attention to some unobvious fact. To make this all work, the parent select must have the CascadingDropDownCategory field specified. If there is a CascadingDropDownBehavior client control that has the parent select as the target control, this field will be specified automatically. Otherwise, you will have to define the field yourself.

But let’s return to the web method. It is called not through the generated proxy, but directly – through the Sys.Net.WebServiceProxy.invoke method. After collecting all the parameters for the web method, the populating (Object, Sys.CancelEventArgs) client-side event is raised, and if the Sys.CancelEventArgs.cancel property wasn’t set to false in the handlers, the request is sent to the server. The web method should return the array of CascadingDropDownNameValue objects.

Screenshot - CascadingDropDownNameValue.jpg

If the web method call fails, then the information about the error (for some unknown for me reason) will be put in the child select as options. If the web method call returns successfully, then the child select options collection will be filled with the received values, and if among one of the received objects, the CascadingDropDownNameValue.value field equals to SelectedValue, or CascadingDropDownNameValue.isDefaultValue == true, its value will be selected. Regardless whether there were or were no mistakes, in the end, the populated(Object, Sys.EventArgs) client-side event is raised. That’s all, the update is finished.

It’s worth mentioning, that at any change of the selected value in the child select, the SelectionChanged(Object, AjaxControltoolkit.CascadingDropDownSelectionChangedEventArgs) client-side event is raised:

Screenshot - CascadingDropDownSelectionChangedEventArgs.jpg

At the moment of writing the article, there was a bug. The LoadingText is set before the populating event is raised, and if the request was cancelled, LoadingText remains in the child select instead of promptText or old options, and this can make the user puzzled.

And when we know more...

Now, when we have more or less got into the principle of work, we can make some conclusions:

  • As CascadingDropDown works via a web method (web service method or PageMethod), it has nothing to do with the server-side or client-side page life-cycles. And, consequently, when the select DOM element is updated with CascadingDropDown, there will be no postbacks, no ASP.NET validators will act, no events of life-cycle will be raised, no SelectedIndexChanged will work etc. Besides, for the work of CascadingDropDown, you do not need to put it in UpdatePanel – it won’t use it all the same.
  • As CascadingDropDown works via a web method, the order of parameters is unimportant. What is important is that the signature of the web method matches the types and the names of the parameters. I.e., the names of the arguments must be the same as given in the documentation. And again, it is indifferent what you use – web service method or PageMethod.
  • CascadingDropDown has no server logic (web method is not counted because it is not part of a control). All that the server-side part of the control performs is help us to simplify the creation of the client-side part. That is why CascadingDropDownBehavior can be created and used separately from server-side and, more than that, without ASP.NET 2.0 at all. Just add the required scripts to the page and use the $create method:
Sys.Application.add_init(function() {
    $create(
        AjaxControlToolkit.CascadingDropDownBehavior, 
        {
            "Category":"Model",
            "ClientStateFieldID":"hiddenFieldClientStateId",
            "ParentControlID":"parentDropDownList",
            "PromptText":"Please select a model",
            "ServiceMethod":"GetDropDownContents",
            "ServicePath":"MyService.asmx",
            "id":"exCascadingDropDown"
        },
        null,
        null,
        $get("childDropDownList")
    );
}); 

The only thing that we haven’t considered here is the ClientStateFieldID property. We need to pass the ID of some hidden-column to this property, so CascadingDropDownBehavior can use this hidden field to store the selected value during postbacks (it is called “ClientState”).

  • Unlike UpdatePanel, requests sent through Sys.Net.WebServiceProxy.invoke are performed independently from each other. That is why we can have concurrency problems on the client if request executions can take a long time. For example: the user has selected some value in the parent select. This action will send the first request. Without waiting for the answer, the user chooses some other value in the same parent select. And here we get the second request. Let’s suppose that to perform the second request, significantly less time is needed than for performing the first one. And as a result, the answer to the second request is given first. The child select has been filled, the user has made up his mind to choose something in it, and at this moment, the answer to the first request is given, and the select is filled with non-valid-at-that-moment values. As a result, the values in the child select do not correspond to the choice in the parent select. I do not know whether or not we can consider this as a bug. However, we will have to resolve this situation on our own.
  • In order to perform client-side validation, control the priority of requests, and to show and hide an animated GIF instead of a “Loading” message, we need to use the populating and populated client-side events. Again, to make it clear: we must subscribe to the events of the CascadingDropDownBehavior that has the child select as its target element, but these events will be raised when the user changes selection in the parent select.
  • Because it is required to have the CascadingDropDownCategory property specified, it is more preferable (though not required) to fill the top DropDownList with CascadingDropDown instead of classic databinding
  • The location of CascadingDropDown in the web form (and, consequently, the order of their initialization) plays an important role. CascadingDropDown, filling the parent select, should be initialized (and placed) first.

And in the end, about those two helper methods I’ve mentioned in the beginning:

  • The ParseKnownCategoryValuesString method parses the knownCategoryValues web method argument from String to StringDictionary. That is rather useful.
  • The QuerySimpleCascadingDropDownDocument method can help you to retrieve a collection of CascadingDropDownNameValue objects from a dataset, based on an existing category/value StringDictionary. I really don’t like datasets, so I don’t use this method. But if you like them, you can see this tutorial.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)