AjaxControlToolkit (ASP.NET/C#) : CascadingDropDown Extender - Part 1
When working with relational data on a frontend one of the common practices
(among TreeViews) are to make use
of DropDowns that update in relation to one another - cascading.
In the following image we've got three DropDownLists that demonstrates this cascading
effect.
When an user selects a country, it updates the list of provinces
(or states)
available for that country, which in turn updates the list of available cities for the
selected province.
One way to achieve this
(many ways to skin a cat) is to attach handlers to the SelectedIndexChanged
events of our DropDownLists and manually handling the cascade relation like seen in the following snippet.
protected void ddlCountry_SelectedIndexChanged(object sender, EventArgs e)
{
ddlCountry.Cascade(ddlProvince, (CountryId) => Province.Get(CountryId));
ddlProvince.Cascade(ddlCity, (ProvinceId) => City.Get(ProvinceId));
}
protected void ddlProvince_SelectedIndexChanged(object sender, EventArgs e)
{
ddlProvince.Cascade(ddlCity, (ProvinceId) => City.Get(ProvinceId));
}
Basically when an user selects a country, we clear all provinces and cities and rebind the province
DropDownList with provinces relating to the selected country. If the user selects a province we clear
the cities DropDownList and rebinds it with cities relating to the selected province.
Now the Cascade method seen in the snippet above is an extension method to which we pass our DataSource via
a lambda expression, the method can be seen in the snippet below.
(also note that all source code for this
post can be downloaded from here)
public static class ExtensionMethods
{
public static void Cascade<T>(this DropDownList parentDropDownList, DropDownList childDropDownList, Func<Int32, T> dataSource)
{
childDropDownList.Items.Clear();
childDropDownList.AppendDataBoundItems = true;
childDropDownList.Items.Add(new ListItem("- Please Select -", ""));
if (!String.IsNullOrEmpty(parentDropDownList.SelectedValue))
{
childDropDownList.DataSource = dataSource(Convert.ToInt32(parentDropDownList.SelectedValue));
childDropDownList.DataBind();
}
}
}
If we need to get rid of the postbacks caused by the cascade we can put our DropDownLists in an UpdatePanel like seen in
the
download accompanying this post.
Another option
(which brings is to the title of this post) is to make use of the CascadingDropDown extender which is part of the
AjaxControlToolkit
Observe the following markup:
<asp:DropDownList runat="server" ID="ddlCountry">
</asp:DropDownList>
<asp:DropDownList runat="server" ID="ddlProvince">
</asp:DropDownList>
<asp:DropDownList runat="server" ID="ddlCity">
</asp:DropDownList>
<asp:CascadingDropDown ID="cddlCountries" runat="server" TargetControlID="ddlCountry"
Category="Country" PromptText="- Please Select -" ServicePath="~/Services/Service.asmx"
ServiceMethod="GetCountries">
</asp:CascadingDropDown>
<asp:CascadingDropDown ID="cddlProvinces" runat="server" TargetControlID="ddlProvince"
ParentControlID="ddlCountry" Category="Province" PromptText="- Please Select -"
ServicePath="~/Services/Service.asmx" ServiceMethod="GetProvinces">
</asp:CascadingDropDown>
<asp:CascadingDropDown ID="cddlCities" runat="server" TargetControlID="ddlCity" ParentControlID="ddlProvince"
Category="City" PromptText="- Please Select -" ServicePath="~/Services/Service.asmx"
ServiceMethod="GetCities">
</asp:CascadingDropDown>
Okay, so what we've got here is our three DropDownLists
(country, province, city) and three CascadingDropDown extenders, here is
quick list of the attributes you're seeing
(there are others) on the extenders.
|
TargetControlID
|
ID of the DropDownList being extended / populated.
|
|
ParentControlID
|
ID of the DropDownList thats above this control in the hierarchy, the extender needs the SelectedValue
of the parent DropDownList in order to be able to databind the target control.
|
|
Category
|
Category represented by the control, this property is used as key(s) to distinguish the values of each
DropDownList SelectedValue in the knownCategoryValues parameter passed to the webmethod - parseable by
the CascadingDropDown.ParseKnownCategoryValuesString method.
|
|
PromptText
|
The text of the default item in the DropDownList (almost feels like something that should have
been part of the DropDownlist WebControl, hint hint). PromptValue sets the value of the default item.
|
|
ServicePath
|
Path to the webservice, uhm... yes you're going to need to write a webservice which will be used by
the extender to databind DropDownLists client side.
|
|
ServiceMethod
|
Method in the webservice thats going to be called in order to populate the DropDownList.
|
In the following code example you can get an idea of what the required web service will look like.
[WebService(Namespace = "http://cstruter.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
[WebMethod]
public CascadingDropDownNameValue[] GetCountries(
string knownCategoryValues,
string category)
{
return Country.Get().Select(p =>
new CascadingDropDownNameValue(p.Title, p.CountryId.ToString())
).ToArray();
}
[WebMethod]
public CascadingDropDownNameValue[] GetProvinces(
string knownCategoryValues,
string category)
{
StringDictionary values = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
Int32 CountryId = Convert.ToInt32(values["Country"]);
return Province.Get(CountryId).Select(p =>
new CascadingDropDownNameValue(p.Title, p.ProvinceId.ToString())
).ToArray();
}
[WebMethod]
public CascadingDropDownNameValue[] GetCities(
string knownCategoryValues,
string category)
{
StringDictionary values = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
Int32 ProvinceId = Convert.ToInt32(values["Province"]);
return City.Get(ProvinceId).Select(p =>
new CascadingDropDownNameValue(p.Title, p.CityId.ToString())
).ToArray();
}
}
Like seen in the example above, each method needs to return an array of CascadingDropDownNameValue - which
is essentially our ListItems.
You might have noticed the methods Country.Get/Province.Get and City.Get, these are all part of the dummy data class provided within the accompanying download to this post.
In the
next part of the post we're going to have a look at how to databind the CascadingDropDown extender
(I've already included the source code for the databinding in the download)
and I am going to share a small concern I've got regarding this extender and some possible solutions.
Posted by - Christoff Truter
Date - 2011-10-07 19:48:59
Comments
Post comment