Welcome to AspAdvice Sign in | Join | Help

.Net Discoveries

An attempt to pass along some answers I have discovered in my .Net coding.
Using the AJAX Control Toolkit’s CollapsiblePanel in a GridView

Prologue

On our company website, the one I mention so frequently (or is it infrequently as often as I post…), we have a data display prominently mounted where it can be viewed by people who are not employees. If they visit our facility, they can view the data (in a GridView) that shows basic information they want to know. The information that feeds the dataset has quite a bit more information that may actually be helpful to various departments within the company, but we don't want to display it to non-employees. What I’d like to do is retain the same GridView but reveal more data when someone in the company clicks the row. Basically, I want to have every other row hidden and then reveal an associated, hidden row when the non hidden row is clicked. (FYI - I also have code I don't talk about in this post that disables this functionality for people who are not inside the company).

Problem

A possible solution that I found was the CollapsiblePanel control that is part of the AJAX Control Toolkit. This provides functionality to do pretty much what I was looking to do. The question is trying to integrate it with a GridView. In my searching on the Internet, I didn’t find much that would suffice for what I wanted to do. One of the closest I found was a kind of an integrated master/detail that I found on a blog. I chose to go ahead and try to use the CollapsiblePanel control. The problem that I found is that you can’t alternate data in a GridView, the row is the row and nothing else. In other words, I found I couldn’t do the 2 things I wanted to with the row 1. hide it (every other row should be hidden), and 2. put different data in it automatically (i.e. different data in every other row, one with public info, one with private). I did get it working, but it's not a wonderfully clean solution, not quite like I expected. Let’s examine how I did it.

Solution

To get started we need to create a new page and add to it a GridView control, it also needs a little configuration so, add the following markup code to your front-end code:

<cc1:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</cc1:ToolkitScriptManager>
<asp:gridview ID="gvwTest" runat="server" AutoGenerateColumns="true"
    AllowSorting="True">
</asp:gridview>

We've just created a very basic GridView, nothing much to look at. We also added a ScriptManager for our CollapsPanelExtender to use later. Next we'll create a data source to populate the GridView. Add the following to your Page_Load event:

gvwTest.DataSource = CreateDataSource()
gvwTest.DataBind()

And then we'll add a helper function CreateDataSource to our application so that it will create and populate the data source,  add the following function:

Public Function CreateDataSource() As DataTable
    Dim dt As New DataTable()
    dt.Columns.Add(New DataColumn("FirstName", GetType(String)))
    dt.Columns.Add(New DataColumn("LastName", GetType(String)))
    dt.Columns.Add(New DataColumn("City", GetType(String)))
    dt.Columns.Add(New DataColumn("State", GetType(String)))

    Dim dr As DataRow
    dr = dt.NewRow()
    dr("FirstName") = "Sally"
    dr("LastName") = "Smith"
    dr("City") = "Phoenix"
    dr("State") = "Arizona"
    dt.Rows.Add(dr)
    dr = dt.NewRow()
    dr("FirstName") = "John"
    dr("LastName") = "Doe"
    dr("City") = "Seattle"
    dr("State") = "Washington"
    dt.Rows.Add(dr)

    Return dt
End Function

This function creates a DataTable and then adds 4 columns, 2 for the name and 2 for their location. Next, we create 2 rows and populate the data. Finally, we return the DataTable.

Now we're ready to get started. If you run your project, you'll see that we have 2 rows each with 4 columns. Let's say though that you'd rather have the location be on the next row and have it revealed only when you click on the name. How do we get 1/2 of one row to display as the 1st row and the 2nd half of the SAME row to display as the 2nd row? The answer is that you can't. If we want to create something custom like that, we need to create a custom TemplateField and this will have to be laid out by hand, I chose to do it by table.

So let's customize our GridView. We'll add support for the collapse panel at the same time. Let's start with how we want to lay it out. We want one row with the full name. We don't want the location to show until the name is clicked, and then we want it to "fly-out" (or down) and reveal the location. What we'll end up with is a table with two rows, one row with name one with location. This table will be nested in EACH row of our GridView. Let's get started by creating a header for our GridView. Add the following between our GridView tags:

<Columns>
    <asp:TemplateField>
        <HeaderTemplate>
        </HeaderTemplate>
    </asp:TemplateField>
</Columns>

We'll be using one actual GridView field, typically each field (in our data) would map to a cell in the row, but we're going to use one cell per GridView row to display our data. Within our HeaderTemplate we'll add a table labeling the data that will be displayed. Add the following inside your HeaderTemplate:

<asp:Table runat="server">
    <asp:TableRow>
        <asp:TableCell>First Name</asp:TableCell>
        <asp:TableCell>Last Name</asp:TableCell>
    </asp:TableRow>
</asp:Table>

That sets the header for the GridView, now for our row. We need to add two tables, one for each "row" we want to present (one for name, one for location). We'll also surround our tables each with a panel so we can target them with the CollapsePanelExtender. All this will be added to our ItemTemplate, add the following just below our HeaderTemplate:

<ItemTemplate>
    <asp:Panel ID="pnlTitle" runat="server">
        <asp:Table runat="server">
            <asp:TableRow>
                <asp:TableCell>
                    <asp:Image ID="imgToggle" ImageUrl="expand.jpg" runat="server" />
                </asp:TableCell>
                <asp:TableCell>
                    <asp:label ID="lblFirstName" Text='<%#Eval("FirstName")%>' runat="server" />
                </asp:TableCell>
                <asp:TableCell>
                    <asp:Label ID="lblLastName" Text='<%#Eval("LastName")%>' runat="server" />
                </asp:TableCell>
            </asp:TableRow>
        </asp:Table>
    </asp:Panel>
    <asp:Panel ID="pnlLocation" runat="server">
        <asp:Table runat="server">
            <asp:TableRow>
                <asp:TableCell>
                    <asp:Label ID="lblCity" Text='<%#Eval("City")%>' runat="server" />
                </asp:TableCell>
                <asp:TableCell>
                    <asp:Label ID="lblState" Text='<%#Eval("State")%>' runat="server" />
                </asp:TableCell>
            </asp:TableRow>
        </asp:Table>
    </asp:Panel>
</ItemTemplate>

You'll notice that I reference an image called 'expand.jpg' this is a little "expander" arrow. The CollapsePanelExtender will actually toggle this image with a 'collapse.jpg' that we'll define later. You can grab any expander and collapser image you like and just direct our code to it (I'm using the ones from the toolkit's sample project). If you run your project, you'll see that our first column has our tables in it and our other columns still appear. We can turn those off by changing our AutoGenerateColumns to false. If you run it again, we should have our tables and no extraneous columns.

Now that we've got everything in place, we just need to get our CollapsPanelExtender working.

We'll need a CollapsePanelExtender for each row that we'll be targeting so we should add the extender inside our ItemTemplate and put it directly after our last panel tag. We'll start with just the basics and add a couple things in a minute. Add the following just below your last panel tag:

<cc1:CollapsiblePanelExtender ID="cpeDetails"
runat="server"
TargetControlID="pnlLocation"
CollapseControlID="pnlTitle"
ExpandControlID="pnlTitle"
Enabled="true" >
</cc1:CollapsiblePanelExtender>

Now if you run your project, we'll have our two "rows" and if you click on the name row, the location row will shrink and if you click it again, it will reveal. Lets modify a couple settings to make it a little better. First let's add functionality for the expander image so it changes automatically, add the following attributes to your cpeDetails extender:

ImageControlID="imgToggle"
CollapsedImage="Collapse.jpg"
ExpandedImage="expand.jpg"
Collapsed="true"

This will set the CollapsPanelExtender to do image swapping and make will make the location panels collapsed by default. One other change to make would to alternate colors for the rows, add the following attribute to your GridView control:

AlternatingRowStyle-BackColor="Silver"

Run it again, and see that it works, we have one "row" with the name and another "row" with the location that can be revealed by clicking on the name.Notice that each row can be expanded and contracted separately from the other rows and that everything is in working order with the collapse and expand images. There we go, a working 'Collapsible' table from a GridView.

Epilogue

You'll notice that I said earlier, this isn't a really clean way to do things, I'll stand by that. The header doesn't necessarily match up with our data, and we're limited on what we can do about that. You can set some cell widths and cell alignments in our tables so that they move where they should be, but you only have so much control over a table, it might expand to fit contents and stuff like that.

However, it is a cheap and easy way to display some extra information pretty easily, and it does look reasonably good in our production environment.

Posted: Tuesday, July 07, 2009 5:19 PM by Yougotiger
Filed under: ,
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Enter the code you see below

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS