AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1674&pId=-1
Complex Data Binding with the Accordion Control
page
by Brian Mains
Feedback
Average Rating: 
Views (Total / Last 10 Days): 64013/ 134

Introduction

The Accordion control is a flexible control that neatly separates the region of the user interface and condenses the overall user interface. This control can support data binding, which I personally did not know until recently. The Accordion control supports binding data to the Header and Content templates, and we will explore how to setup some more complex interfaces of these scenarios in this article.

Header/Content Template

Within the accordion control, it is possible to bind data to the header and content templates. However, the header and content template have a one-to-one relationship, meaning that one header template matches one content template. Let me illustrate this; look at the following example which binds Customer records to an accordion.

Listing 1

<ajax:Accordion id="accSingleData" runat="server">
  <HeaderTemplate>
    <asp:Label ID="lblLastName" runat="server" Text='<%# Eval("LastName") %>' />
  </HeaderTemplate>
  <ContentTemplate>
    <asp:Label ID="lblFirstName" runat="server" Text='<%# Eval("FirstName") %>' />
  </ContentTemplate>
</ajax:Accordion>

If you expect this example to group entries by last name, it will not; rather, a pane is created for each customer, with the last name as the header and the first name as the content. It is a one-to-one relationship.

Parent/Child Data

How can we show parent/child data, where the header template represents a parent object and the content panel represents child data related to that parent? To illustrate, I am going to use the following database structure.

Figure 1

For the accordion, the header for each accordion pane will be the customer name. Each customer has zero or more orders displayed in the content template and these orders belong to the customer. These orders will be displayed below the customer in a GridView list, using the template below.

Listing 2

<ajax:Accordion ID="accComplexData" runat="server">
<HeaderTemplate>
Customer:
<asp:Label ID="lblLastName" runat="server" Text='<%# Eval("LastName") %>' />,
<asp:Label ID="lblFirstName" runat="server" Text='<%# Eval("FirstName") %>' />
</HeaderTemplate>
<ContentTemplate>
Joined on:
<asp:Label ID="lblJoinedDate" runat="server" Text='<%# Eval("CreatedDate") %>' />
<br /><br />
<asp:GridView ID="gvwOrders" runat="server" 
 DataSource='<%# Eval("Orders") %>' DataKeyNames="OrderKey">
<Columns>
<asp:BoundField HeaderText="Total Amount" DataField="TotalAmount" />
<asp:BoundField HeaderText="Reference" DataField="ReferenceNumber" />
<asp:BoundField HeaderText="Order Date" DataField="OrderDate" 
 DataFormatString="{0:MM/dd/yyyy}" />
</Columns>
<EmptyDataTemplate>No orders have been submitted.</EmptyDataTemplate>
</asp:GridView>
</ContentTemplate>
</ajax:Accordion>

When the accordion binds, a new accordion pane represents the Customer record. The Customer object has an Orders property related to that customer, and is the source for the GridView control. However, the content pane can still contain information about the customer because both the header and content template are bound to the Customer object.

If you try to change the data source, such as to refresh the grid, it may be better to handle binding the grid in the Accordion's ItemDataBound event handler. This event fires once for each bound item in the accordion. To access the controls in the row, use the following approach.

Listing 3

void accComplexData_ItemDataBound(object sender, 
AjaxControlToolkit.AccordionItemEventArgs e)
{
   if (e.ItemType != AjaxControlToolkit.AccordionItemType.Content)
   return;
   Customer customer = e.Item as Customer;
   if (customer == nullreturn;
   GridView grid = e.AccordionItem.FindControl("gvwOrders"as GridView;
   if (grid == nullreturn;
   grid.DataSource = customer.Orders;
   grid.DataBind();
}

This approach retrieves a reference to the GridView control via FindControl. Once found, the reference to the Customer object for that accordion pane is accessible through the Item property.  The Customer object can then have its Orders collection bound to the grid.

The reason this may be better is because if you try to rebind the grid later individually, you may receive an error when trying to bind to a different result set manually. I tried leaving both the DataSource Eval expression in the grid, as well as the code that binds in code-behind, and the following result is an error:  "Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control."

How could the underlying grid be refreshed in cases of updates occurring elsewhere?  There are two options to refresh the grid; the first option is to rebind the accordion each time. Let us look at a few approaches. The first approach is to rebind the entire accordion. For this to work, I have added a refresh button that simply rebinds the grid with all of the new customer information.

Listing 4

private void BindAccordions()
{
  CustomerBAL bal = new CustomerBAL(this.DataContext);
  this.accComplexData.DataSource = bal.GetAll();
  this.accComplexData.DataBind();
}

As another approach, because each accordion item contains a grid, it is possible to rebind data to a single grid. To do that, some information about the key for the customer for the current record needs stored in that row. What this means is that with each grid there needs to be some way to track the key for the customer that accordion item represents. I usually use a hidden field.

Listing 5

<asp:HiddenField ID="hdnCustomerKey" runat="server" 
    Value='<%# Eval("CustomerKey") %>' />

An alternative approach is to define the CommandArgument property of the button.

Listing 6

<asp:Button ID="btnRefresh" runat="server" Text="Refresh" CommandName="Refresh" 
CommandArgument='<%# Eval("CustomerKey") %>' />

I will use the last approach in the code below; however, both approaches keep the customer key with the grid. When the refresh button for each grid is clicked, the key is retrieved from the command argument property through the button reference. Using the key, the Customer record is queried from the database using the key and the Orders are bound to the grid's reference, also retrieved using the FindControl method.

Listing 7

void accComplexData_ItemCommand(object sender, CommandEventArgs e)
{
  if (e.CommandName != "Refresh")
  return;
  GridView grid = (GridView)this.accComplexData
.Panes[this.accComplexData.SelectedIndex].ContentContainer.FindControl("gvwOrders");
  CustomerBAL bal = new CustomerBAL(this.DataContext);
  Customer customer = bal.GetByKey((Guid)e.CommandArgument);
  this.BindGrid(grid, customer);
}

The actual binding of the grid is simple; I always pull binding information out of the event handler code and use a separate method as defined below.

Listing 8

private void BindGrid(GridView grid, Customer customer)
{
  grid.DataSource = customer.Orders;
  grid.DataBind();
}

However, this could use some refactoring. Rather than retrieving the customer reference in the event handler, it would be better to pass in the GUID instead. This moves the data access calls into one place, instead of repeating the same code separately. This has been refactored as follows.

Listing 9

private void BindGrid(GridView grid, Guid customerKey)
{
  CustomerBAL bal = new CustomerBAL(this.DataContext);
  Customer customer = bal.GetByKey(customerKey);
  if (customer == null)
  throw new NullReferenceException(string.Format("The customer for {0} is null",
  customerKey));
  grid.DataSource = customer.Orders;
  grid.DataBind();
}

This method can be called with: this.BindGrid(grid, (Guid)e.CommandArgument); .

This is a little cleaner and the BindGrid method can be used in multiple situations by passing in the GUID, and the data access code is all in the same place. I think this is a better approach to take and we will see that it turns out useful in the following section.

Grid Selection

To setup selection for the grid, I changed the grid definition to the following:

Listing 10

<asp:GridView … AutoGenerateSelectButton="true" 
OnSelectedIndexChanging="gvwOrders_SelectedIndexChanging">

To select the grid when the grid is bound manually, the SelectedIndexChanging event needs handled so that the grid can be rebound to show the current selected item.

Listing 11

protected void gvwOrders_SelectedIndexChanging(object sender, 
GridViewSelectEventArgs e)
{
  GridView grid = (GridView)sender;
  HiddenField customerKeyField = (HiddenField)grid.FindControl("hdnCustomerKey");
  this.BindGrid(grid, new Guid(customerKeyField.Value));
}

Using the BindGrid refactoring, passing in the grid works well to incorporate two scenarios.  However, you do not really need to handle the selection approach from the grid (in a manual bind scenario).

Grid Editing

The gridview fires the RowEditing event whenever the mode changes from read-only to displaying an edit interface. To invoke editing, click a button with the CommandName set to Edit. The grid needs three things: the AutoGenerateEditButton property needs set to true, the RowEditing event needs an event handler attached to it, and the grid needs rebound with the EditIndex property set to the current row (before the data bind). Take a look at the approach below. This event is wired up to each grid in the accordion.

Listing 12

protected void gvwOrders_RowEditing(object sender, GridViewEditEventArgs e)
{
  GridView grid = (GridView)sender;
  grid.EditIndex = e.NewEditIndex;
  HiddenField customerKeyField = (HiddenField)grid.FindControl("hdnCustomerKey");
  this.BindGrid(grid, new Guid(customerKeyField.Value));
}

Notice that the method pulls the key from the hidden field setup in the accordion template, and uses the same BindGrid method to repopulate the grid.

Update works in a similar manner; any updates need to be performed manually, as shown below.

Listing 13

protected void gvwOrders_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
  GridView grid = (GridView)sender;
  GridViewRow row = grid.Rows[e.RowIndex];
  string totalAmount = ((ITextControl)row.Cells[1].Controls[0]).Text;
  string referenceNumber = ((ITextControl)row.Cells[2].Controls[0]).Text;
  string createdDate = ((ITextControl)row.Cells[3].Controls[0]).Text;
 
  //Update the record
}

Each field to update is extracted by retrieving the value from the textboxes that hold the value. I did not show it above, but the values pulled back from the textboxes are passed to the customer record for the selected customer. This record is then submitted to the database.

Conclusion

The accordion is handy to create a specific application design. It also supports data binding using the approaches above.

Important Link

Accordion control



©Copyright 1998-2019 ASPAlliance.com  |  Page Processed at 2019-03-24 11:53:48 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search