Populating Related Drop Down Lists With AJAX
(
Jul 05 2007 - 05:23:03 PM by
Rob Meade) - [
print article]
Overview
Prior to ASP.Net the way we would add related drop down lists was by creating arrays in JavaScript and writing them out to the page. Due to the size and nature of the organization I work for (the NHS in the UK) these arrays could be considerable in size and typically made the page bloated.
For me JavaScript arrays always meant large amounts of data being written to the page. The majority of the data would be unnecessary for the bulk of users. Tabs and carriage returns in the JavaScript would normally be added (and often left in) to make the data more readable for debugging purposes. All of this combined with the actual data in the arrays themselves could create very large page sizes.
With my recent experimentation with AJAX and the start of a new project I was curious to see if it could help solve the problem in a better way, the following is a small example that might help some of you who wish to populate related drop down lists without having to store all possibilities in the page, but instead calling the data from the server as needed.
Getting Started
Lets start by creating a new AJAX Enabled Web Site.
Adding Some Controls
With our Default.aspx page in Design View, let’s add an UpdatePanel to our page. This should be placed beneath the ScriptManager control which has been added for us.
The UpdatePanel will provide us with an area of the screen that we can update asynchronously, in it we need to place our controls, specifically the ones we want to see update, lets add our two DropDownList controls to the UpdatePanel:
To make things a bit easier to follow, we will update the IDs of the two DropDownLists to something a little more meaningful. So we'll name them ddlShows and ddlCharacters respectively. If you now view the source of our page (default.aspx) it should resemble the following:
<form runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:DropDownList ID="ddlShows" runat="server" />
<asp:DropDownList ID="ddlCharacters" runat="server" />
</ContentTemplate>
</asp:UpdatePanel>
</form>
A Little Bit About Triggers
In order for our UpdatePanel to actually do anything it needs to know what the trigger will be, meaning what is going to happen in order for the UpdatePanel to update.
By default the UpdatePanel has its ChildrenAsTriggers property set to True, so the two DropDownLists that we have added to our UpdatePanel will already act as Triggers, however, I like to actually see and know what's going on in my applications, so we're going to change the ChildrenAsTriggers property to False and add the Trigger manually.
There are also other reasons why you may not want ChildrenAsTriggers set to True, an example of which is mentioned in the AJAX online documentation. If you were to have two UpdatePanel controls and you want a PostBack from the first panel to update the content of the second panel but not update its own content.
Another reason for adding Triggers manually might be if the control you want to use as a Trigger is NOT inside the UpdatePanel but located elsewhere on your page.
One important thing to bare in mind is that if we change our ChildrenAsTriggers property to False we also need to change the UpdateMode property of our UpdatePanel from its default setting Always, to Conditional. If we do not do this an InvalidOperationException will be thrown because the combination of properties is not allowed.
An UpdateMode of Always means that the UpdatePanel will update with every PostBack, with it's UpdateMode set to Conditional the UpdatePanel will only update when one of its Triggers causes it to.
Adding Our Trigger
Flick back to DesignView, select the UpdatePanel on the page and in the Properties window change the ChildrenAsTriggers property to False.
We will now add our Trigger, with the UpdatePanel still selected, click on the ellipsis (…) button for Triggers (located under Behavior) in the Properties window. The UpdatePanelTrigger Collection Editor window is now displayed. Click on the downward arrow on the Add button to select the type of trigger we want, in this case the AsyncPostBack Trigger.
Now click in the ControlID field and select our first DropDownList (ddlShows), click in the Event field and select SelectedIndexChanged, click OK.
In order for our Trigger to work fully we need to ensure that the control which will act as a Trigger can post back to the server. Select our first DropDownList (ddlShows) and change its AutoPostBack property to True. If you view the source for our page (default.aspx.vb) it should resemble the following:
<form runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:DropDownList ID="ddlShows" runat="server" AutoPostBack="True" />
<asp:DropDownList ID="ddlCharacters" runat="server" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="ddlShows" EventName="SelectedIndexChanged" />
</Triggers>
</asp:UpdatePanel>
</form>
As you can see the UpdatePanelTrigger Collection Editor has automatically added the code for the Triggers section of the UpdatePanel for us, you can have many triggers in this section, all of which can trigger the UpdatePanel to update but for this example one is all we need.
How About Some Data
We could just jump straight in and hard code some values, but I prefer to build some supporting methods which can be used time and time again, this could well help us to expand the project at a later stage.
To start with we'll add the following method which will define the structure of our DataTable. Our DataTable will contain two DataColumns, the first will store an ID for the items added to the DataTable, the second will hold the actual text that we want to display for the DropDownList items.
Private Sub DefineDataTable(ByRef dataTable As Data.DataTable)
Dim dataColumn As Data.DataColumn
dataColumn = New Data.DataColumn
dataColumn.ColumnName = "ID"
dataColumn.DataType = Type.GetType("System.Int32")
dataColumn.AutoIncrement = True
dataColumn.AutoIncrementSeed = 0
dataColumn.AutoIncrementStep = 1
dataTable.Columns.Add(dataColumn)
dataTable.Columns.Add("Text", Type.GetType("System.String"))
dataColumn.Dispose()
End Sub
In the above code you can see that the ID column requires a little more work to define than the String column, in addition you'll notice that the AutoIncrementSeed has been set to start at 0 (zero) - this is by design and will be explained later.
Next we'll add a method which will enable us to populate DataRows that are contained within our DataTable.
Private Sub PopulateDataRow(ByRef dataTable As Data.DataTable, ByVal text As String)
Dim dataRow As Data.DataRow
dataRow = dataTable.NewRow
dataRow("Text") = text
dataTable.Rows.Add(dataRow)
End Sub
With our DataTable structure definable and a method to populate it, lets add two more supporting methods which will create new ListItems and populate our DropDownLists.
Private Function CreateListItem(ByVal value As Integer, ByVal text As String) As ListItem
Dim listItem As ListItem
listItem = New ListItem
listItem.Value = value
listItem.Text = text
Return listItem
End Function
Private Sub PopulateDropDownList(ByRef dropDownList As DropDownList, ByVal id As Integer, ByVal text As String)
dropDownList.Items.Add(CreateListItem(id, text))
End Sub
CreateListItem tables a value and some text and uses these to populate its properties, we can call this method many times to create ListItems for either of our DropDownLists - reuse!
PopulateDropDownList is passed a reference to a DropDownList, an integer and a string. By telling this method which DropDownList to populate we can use it for both ddlShows and ddlCharacters - reuse! We'll create the following method to populate the DropDownLists with data:
Private Sub PopulateShows()
Dim dataTable As Data.DataTable
Dim dataRow As Data.DataRow
dataTable = New Data.DataTable
DefineDataTable(dataTable)
PopulateDataRow(dataTable, "Please Select")
PopulateDataRow(dataTable, "AirWolf")
PopulateDataRow(dataTable, "KnightRider")
PopulateDataRow(dataTable, "The A-Team")
For Each dataRow In dataTable.Rows
PopulateDropDownList(ddlShows, dataRow.Item("ID"), dataRow.Item("Text"))
Next
End Sub
PopulateShows will use all of our supporting methods in order to populate ddlShows with the three 80's TV shows shown above (AirWolf, KnightRider, The A-Team).
In it we first create a new instance of a DataTable object; we then call our DefineDataTable method passing it a reference to our DataTable. With the structure defined we can now add rows of data to it by calling PopulateDataRow.
You'll notice that the first of these calls is for "Please Select", this is why we set the AutoIncrementSeed to 0 (zero) earlier on. Often you'll find that the "Please Select" values will be 0 (zero) or "". All of the shows will have IDs 1, 2 and 3 respectively.
Next we need a method to populate the characters for these shows in our second DropDownList (ddlCharacters) so we'll create the following method:
Private Sub PopulateCharacters(ByVal show As String)
Dim dataTable As Data.DataTable
Dim dataRow As Data.DataRow
dataTable = New Data.DataTable
DefineDataTable(dataTable)
PopulateDataRow(dataTable, "Please Select")
Select Case show
Case "1"
PopulateDataRow(dataTable, "Dominic Santini")
PopulateDataRow(dataTable, "Stringfellow Hawke")
Case "2"
PopulateDataRow(dataTable, "Kit")
PopulateDataRow(dataTable, "Michael Knight")
Case "3"
PopulateDataRow(dataTable, "B.A")
PopulateDataRow(dataTable, "Face")
PopulateDataRow(dataTable, "Hannibal")
PopulateDataRow(dataTable, "Murdock")
End Select
ddlCharacters.Items.Clear()
For Each dataRow In dataTable.Rows
PopulateDropDownList(ddlCharacters, dataRow.Item("ID"), dataRow.Item("Text"))
Next
End Sub
Once again we are able to use all of our supporting methods - reuse! The show ID is passed to this method and will be used to determine which characters will populate ddlCharacters. Note also the call to clear the existing items, without this, each time we select a show more and more characters will be added to ddlCharacters.
Wrap It Up
We have everything we need to populate a DropDownList based on the selection of another DropDownList using AJAX except for a few lines of code which we will now add to the Page_Load method (default.aspx.vb).
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Page.IsPostBack = True Then
If ScriptManager1.IsInAsyncPostBack = True Then
Select Case ScriptManager1.AsyncPostBackSourceElementID.ToLower
Case "ddlshows"
PopulateCharacters(ddlShows.SelectedValue.ToLower)
End Select
End If
Else
PopulateShows()
End If
End Sub
In the above code we first check to see if the page has posted back to the server, if it hasn't then we call PopulateShows which will fill ddlShows with our three 80's tv shows. If the page has posted back then we check to see if it has been posted asynchronously, in this example we're assuming this will be the case as no other controls are present on the form.
Next we want to determine which control has caused the asynchronous post back, we obtain the ID of the control by using our ScriptManager object and its AsyncPostBackSourceElementID property. If our asynchronous post back was caused by a SelectedIndexChanged event on ddlShows the new call PopulateCharacters and pass in the ID of the show that was selected.
Run It!
So, we build and run the application. You see that the shows are populated on the Page_Load, you select one and the characters are populated in the second DropDownList. What's to say this was done with AJAX and not just that it all happened so fast that you didn't notice your browser loading the new page? Well, lets do one final thing to prove it.
Return to the design view and above the UpdatePanel add a Label. Select the Label and in the Properties window change its ID to lblDateTime and remove the text value. Now lets amend our PopulateShows method so that when it's called it updates the Text property of lblDateTime to equal the current date and time. Amend PopulateShows so that it resembles the following:
Private Sub PopulateShows()
Dim dataTable As Data.DataTable
Dim dataRow As Data.DataRow
dataTable = New Data.DataTable
DefineDataTable(dataTable)
PopulateDataRow(dataTable, "Please Select")
PopulateDataRow(dataTable, "AirWolf")
PopulateDataRow(dataTable, "KnightRider")
PopulateDataRow(dataTable, "The A-Team")
For Each dataRow In dataTable.Rows
PopulateDropDownList(ddlShows, dataRow.Item("ID"), dataRow.Item("Text"))
Next
lblDateTime.Text = Now
End Sub
Build and run the application again. Because PopulateShows is only called when the page is not being posted back we know that the date/time displayed in the label will only occur once. Now select a show and you'll see again that the characters are updated in ddlCharacters and the date/time does not change.
Thanks for Reading
I hope you found this, my first article, easy to follow and that it has been of some use to you. Review the source code for yourself to see how it's done. Here's a link to a zip file containing the web site project.