Revisited: Adding Non-Selectable ListItem Controls To An ASP.NET DataBound List Control

Last August I had quickly blogged on the subject of adding non-selectable ListItem controls to an ASP.NET DropDownList. At the time I didn’t have an easy way to demonstrate how that code worked — and, in all honesty, I hadn’t tested it thoroughly, so it had some problems.

But now, thanks to GoDaddy’s dirt-cheap ASP.NET Web hosting, I can run dougv.net, the ASP.NET demo site I had promised some time ago.

I’ve finally gotten around to configuring that new site and getting it to run the way I want, so at long last I can post this revisiting of the original post, which has actually been sitting around on my hard drive since last September.

Let’s first define some terms for less-experienced ASP.NET developers.

Control: An ASP.NET control is any Web page element — a text box, select list, image, div, whatever — that either allows the user to change its value (e.g., a text box or select list) or that had its contents / value / rendering changed on the server side (such as a GridView that is populated by a SqlDataSource).

So for most intents and purposes, an ASP.NET control is any Web page element that runs at the server.

Databound: A control is databound when its contents / options have been set with specific information. For example, a select list (DropDownList) is databound as soon as we add an option (ListItem) to it.

Some resources on the Web claim databinding only takes place if you use an external data store, such as an XML file or SQL Server database, to populate the child controls of a databound control (for example, to add ListItem controls to a DropDownList). That is incorrect: A control is technically databound if we set any of its properties. For example, a TextBox control is technically databound if we initially set its Text property.

OK, with those definitions out of the way, let’s visit the next obvious question: Why in the world would you want a non-selectable ListItem?

Because often, the number of items in a list-style control — namely, a select / DropDownList, but in ASP.NET, also a CheckBoxList or RadioButtonList control — can get unwieldy. If you have a list of, say, 30 options in a select list, it’s going to be difficult to pick the 17th option out of that list.

But if you broke the list up into groups of 5, it’s as easy as going to the fourth group and picking the second item from the top. That’s where the non-selectable ListItem comes in: We create an option that only serves the purpose of breaking up our list visually; it does not contain a valid value, and we don’t want to let users send that invalid value to our application.

Consider: Which of these lists is easier to read?

03901    BERWICK
03902    CAPE NEDDICK
03903    ELIOT
03903    SOUTH ELIOT
03904    KITTERY
03905    CUTTS ISLAND
03905    KITTERY POINT
03906    NORTH BERWICK
03907    OGUNQUIT
03908    SOUTH BERWICK
03909    YORK
03909    YORK CENTER
04001    ACTON
04002    ALFRED
04003    BAILEY ISLAND
04004    BAR MILLS
04005    ARUNDEL
04005    BIDDEFORD
04005    DAYTON
04005    LYMAN
04006    BIDDEFORD POOL
04008    BOWDOINHAM
04009    BRIDGTON
04009    SOUTH BRIDGTON
04009    WEST BRIDGTON
04010    BROWNFIELD
04010    EAST FRYEBURG
04010    WEST DENMARK
04011    BIRCH ISLAND
04011    BRUNSWICK
03901    BERWICK
03902    CAPE NEDDICK
03903    ELIOT
03903    SOUTH ELIOT
03904    KITTERY

03905    CUTTS ISLAND
03905    KITTERY POINT
03906    NORTH BERWICK
03907    OGUNQUIT
03908    SOUTH BERWICK

03909    YORK
03909    YORK CENTER
04001    ACTON
04002    ALFRED
04003    BAILEY ISLAND

04004    BAR MILLS
04005    ARUNDEL
04005    BIDDEFORD
04005    DAYTON
04005    LYMAN

04006    BIDDEFORD POOL
04008    BOWDOINHAM
04009    BRIDGTON
04009    SOUTH BRIDGTON
04009    WEST BRIDGTON

04010    BROWNFIELD
04010    EAST FRYEBURG
04010    WEST DENMARK
04011    BIRCH ISLAND
04011    BRUNSWICK

The blank spaces in the right-hand list above represent where we might insert non-selectable ListItem controls to “break up” the list.

Of course, the limitations of the Web are that you can’t easily prevent end users from choosing a specific option from a select list. Sure, you can do it with JavaScript (and I would be willing to blog on how to do so if anyone asks), but you can’t rely on JavaScript to provide your applications with good data.

Fortunately, ASP.NET makes evaluating a non-selectable ListItem as simple as checking its value. In other words, as long as we explicitly declare the Value property of a ListItem control to be an empty string, we can make that ListItem non-selectable.

Example 1: The Basics

Let’s start by hand-coding a DropDownList with several ListItems. We’ll add in a Label control that we will change based on the selected item in the DropDownList, and we’ll set the DropDownList’s AutoPostBack property to true, so that every time we change selections, the page will be sent back to the server for processing.

<asp:DropDownList runat="server" ID="ddlVacation" AutoPostBack="true" OnSelectedIndexChanged="ddlVacation_postBack">
 <asp:ListItem Text="-- Sunny Island -- " Value="" />
 <asp:ListItem Value="Aruba" />
 <asp:ListItem Value="Hawaii" />
 <asp:ListItem Value="Bermuda" />
 <asp:ListItem Text="-- Snowy Getaway --" Value="" />
 <asp:ListItem Value="Vail, CO" />
 <asp:ListItem Value="Nome, AK" />
 <asp:ListItem Value="Greenland" />
 <asp:ListItem Value="The North Pole" />
 <asp:ListItem Value="Antartica" />
 <asp:ListItem Text="-- Big Adventure --" Value="" />
 <asp:ListItem Value="Mt. Everest" />
 <asp:ListItem Value="The Amazon River" />
</asp:DropDownList>
<asp:Label runat="server" ID="lblMessage" Text="Select a vacation" />

Note that we explicitly set the value of the non-selectable items to be an empty string. Again, that’s how we’re going to determine if the ListItem is non-selectable; if it contains no value, we know the user shouldn’t have selected it.

We can do this programmatically very easily. We simply ask if the SelectedValue property of the DropDownList — that is, the value of the ListItem which is currently selected — is an empty string. If so, the Label control is changed to indicate the option is not available; if not, the Label control is changed to reflect the choice made.

Sub ddlVacation_postBack(ByVal Sender As Object, ByVal E As EventArgs)
    If ddlVacation.SelectedValue = String.Empty Then
        lblMessage.Text = "Sorry, that's not an option. Please choose again."
        lblMessage.Font.Bold = True
        lblMessage.ForeColor = Drawing.Color.Red
    Else
        lblMessage.Text = "I would love to go to <strong>" & ddlVacation.SelectedValue & "</strong>!"
        lblMessage.Font.Bold = False
        lblMessage.ForeColor = Drawing.Color.Black
    End If
End Sub

Working demo: http://www.dougv.net/demos/non_selectable_listitem/default.aspx

Example 2: Periodic Separators In A DataBound Control

A more useful tactic is automatically programming separators into a collection of ListItems that you have retrieved from an external data store, such as a database.

For example, I have a database table that lists all 50 US states plus their postal codes. Perhaps I want this list to be easier on my users’ eyes and make it easier to pick out a given state.

To do that, I will break the list up into 10-state sections. And to accomplish that, I am going to have to get old school and programmatically bind the database’s result set to the DropDownList control.

First, let’s look at the control / page cod, which is the simple control declarations: the DropDownList, a Label and a Button.

<asp:DropDownList runat="server" ID="ddlMyList" />
<asp:Label runat="server" ID="lblStatus" Text="Select a town from the list and click Get Postal Code." />
<br />
<asp:Button runat="server" ID="btnSubmit" OnClick="btnSubmit_onClick" Text="Get Postal Code" />

The codebehind is a bit more complex, because we need to open a connection to the database, query it for records, then create the ListItems from those records, periodically inserting our non-selectable separators.

We use three subroutines to accomplish this task: Page_Load is used to trigger the initial population of the DropDownList. ddlMyList_dataBind actually adds the ListItems to the control. And btnSubmit_click handles the postback.

Sub Page_Load(ByVal Sender As Object, ByVal E As EventArgs) Handles Me.Load
    If Not Page.IsPostBack Then
        'bind the dropdownlist
        ddlMyList_dataBind()
    End If
End Sub

Sub ddlMyList_dataBind()
    'Subroutine that binds listitems to dropdownlist
    'This requires the System.Data and System.Data.SqlClient namespaces

    'Open connection to database; declare SQL statement
    'You really should use parameterized stored procedures, but we'll do this as straight SQL
    Dim objConn As New SqlConnection("my connection string")
    Dim objCmd As New SqlCommand("SELECT * FROM states_table ORDER BY state_name ASC", objConn)
    objCmd.CommandType = CommandType.Text

    'We need a generic loop counter
    Dim I As Integer = 0

    Try
        'connect to db
        objConn.Open()
        'create a data reader and populate it with SQL statement results
        Dim objReader As SqlDataReader = objCmd.ExecuteReader()

        'if the reader has records, let's bind the results
        If objReader.HasRows() Then
            'go through all items in the recordset
            'we'll read the items one by one
            Do While objReader.Read()
                'add a heading at the top of the list and every 10 additional items
                If I Mod 10 = 0 And I < 50 Then
                    ddlMyList.Items.Insert(I, New ListItem("--------------------", String.Empty))
                Else
                    'add the current data reader item
                    ddlMyList.Items.Insert(I, New ListItem(objReader("state_name"), objReader("state_code")))
                End If
                'increment the loop counter
                I += 1
            Loop
        Else
            'report that there are no items to display in the list
            ' and disable the dropdownlist
            lblStatus.Text = "There are no records for this item"
            ddlMyList.Enabled = False
        End If

        'close the connection
        objConn.Close()

    Catch ex As Exception
        'if there is an error, report it
        ' and disable the dropdownlist
        lblStatus.Text = ex.Message
        ddlMyList.Enabled = False
    End Try

    'clean up the database objects
    objCmd.Dispose()
    objConn.Dispose()
End Sub

Sub btnSubmit_onClick(ByVal Sender As Object, ByVal E As EventArgs) Handles btnSubmit.Click
    'report when a non-selectable item has been selected
    'non-selectable items have no values
    If String.IsNullOrEmpty(ddlMyList.SelectedValue) Then
        lblStatus.Text = "You cannot select this item. Please select a different item."
        lblStatus.ForeColor = Drawing.Color.Red
        lblStatus.Font.Bold = True
    Else
        lblStatus.Text = "Postal code: " & ddlMyList.SelectedValue
        lblStatus.ForeColor = Drawing.Color.Black
        lblStatus.Font.Bold = False
    End If
End Sub

Note that we check for postback in the Page_Load subroutine. Every time btnSubmit is clicked, the page is sent via postback. If we didn’t check, therefore, whether the page was being posted back, we would databind anew the DropDownList every time the page is loaded — effectively wiping out whatever choice we made.

Working demo: http://www.dougv.net/demos/non_selectable_listitem/databound.aspx

Example 3: With A CustomValidator Control

This is what I was after with my initial post: A simple way to use ASP.NET’s built-in validation controls to do what they are designed to do, namely, validate input.

The problem is that ASP.NET List controls are not amenable to validation controls out of the box. Basically, you need to create a CustomValidator that isn’t specifically set to validate the given control, then write an explicit OnServerValidate event handler that uses the List control’s name, rather than the passed ServerValidateEventArguments.

That sounds more complicated than it actually is. Let’s start with the page / control code:

<asp:DropDownList runat="server" ID="ddlVacation" AutoPostBack="true" OnSelectedIndexChanged="ddlVacation_postBack">
    <asp:ListItem Text="-- Sunny Island -- " Value="" />
    <asp:ListItem Value="Aruba" />
    <asp:ListItem Value="Hawaii" />
    <asp:ListItem Value="Bermuda" />
    <asp:ListItem Text="-- Snowy Getaway --" Value="" />
    <asp:ListItem Value="Vail, CO" />
    <asp:ListItem Value="Nome, AK" />
    <asp:ListItem Value="Greenland" />
    <asp:ListItem Value="The North Pole" />
    <asp:ListItem Value="Antartica" />
    <asp:ListItem Text="-- Big Adventure --" Value="" />
    <asp:ListItem Value="Mt. Everest" />
    <asp:ListItem Value="The Amazon River" />
</asp:DropDownList>
<asp:Label runat="server" ID="lblMessage" Text="Select a vacation" />
<asp:CustomValidator runat="server" ID="cvVacation" OnServerValidate="cvVacation_serverValidate" ForeColor="Red" Font-Bold="true" ErrorMessage="Sorry, that's not an option. Please try again." Display="Dynamic" />

And now, the codebehind. Note that every time we call the OnSelectedIndexChanged event handler for the DropDownList, we need to validate the page; that is the only way to get the CustomValidator to fire. If we don’t call Page.Validate, the CustomValidator will not be invoked at all.

Sub ddlVacation_postBack(ByVal Sender As Object, ByVal E As EventArgs)
    Page.Validate()
    If Page.IsValid Then
        lblMessage.Text = "I would love to go to <strong>" & ddlVacation.SelectedValue & "</strong>!"
    End If
End Sub

Sub cvVacation_serverValidate(ByVal Sender As Object, ByVal E As ServerValidateEventArgs)
    If ddlVacation.SelectedValue = String.Empty Then
        E.IsValid = False
        lblMessage.Text = ""
    End If
End Sub

Working demo: http://www.dougv.net/demos/non_selectable_listitem/validator.aspx

Notice that this is startlingly similar to Example 1. In fact, you could argue that Example 1 is more elegant. So why bother with the CustomValidator?

One thought is that you might want to validate all your controls only once, at the time the page is submitted. If so, you can simply add Page.Validate to the subroutine or function that handles your postback processing, so that all controls are fired at once.

You might also want to validate proper input as part of a group of items on a page. For example, you might have a DropDownList that gives a series of state names, and a textbox next to it that asks for a ZIP Code; it may be that you want to ensure a proper state is chosen, then ensure the ZIP Code provided is valid for the state given, before proceeding to another part of the form.

To do that, you would set the ValidationGroup property of both controls’ validators — for the DropDownList, a custom validator; for the TextBox, perhaps another CustomValidator, or perhaps a CompareValidator or RegularExpressionValidator — to be the same, so that they effectively validate as a single item.

All the links in this post on delicious: http://delicious.com/dhvrm/revisited-adding-non-selectable-listitem-controls-to-an-aspnet-databound-list-control

One thought on “Revisited: Adding Non-Selectable ListItem Controls To An ASP.NET DataBound List Control

  1. Pingback: dougv.com | The Web home of Doug Vanderweide » Blog Archive » Using AJAX To Data Bind A Child Drop Down List Based On The Selected Option Of A Parent Select Control

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current ye@r *