Tuesday, February 10, 2009

ASP.NET GridView With Simple Collapse and Expand Functionality

I recently needed an expandable and collapsible GridView in a project I was working on, but couldn't find any simple solutions that met my criteria. Fortunately, it was fairly easy to write up. I have included the basic source code below, so it should be fairly easy to implement into other projects. The only thing you will need to do is find a collapse and expand icon and then update the image names accordingly in the code behind. (For SharePoint, you can use "_layouts\images\TPMin2.gif" for collapse and "_layouts\images\TPMax2.gif" for expand.)

Note: I have not included code for paging. This will require a little more work.

Requirements

  • Use a standard ASP.NET GridView control.
  • Ability to collapse or expand the whole GridView or selective parts of it.
  • Use client side Javascript to ensure better performance and no browser refresh.
  • Only show expand and collapse buttons and grouping value ("Company" in my case) if the next row has the same grouping value.
  • Provide the ability to have the GridView collapsed initially. This is handled in the OnRowDataBound event handler.

Smoke Tests

  • Collapse / Expand link works as expected and the text is updated accordingly. - PASS
  • Collapse and expand button icons are updated properly when clicked and when using the Collapse / Expand link. - PASS
  • Rows without expand and collapse behave properly, especially the first and last rows. - PASS

Screenshots

All Expanded

image

All Collapsed

image

Some Expanded and Some Collapsed

image

ASPX Code

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>

    <script type="text/javascript">
        function ExpandCollapse(index, imageName) {
            var continueLoop = new Boolean(true);
            var imageMax = "TPMax2.gif";
            var imageMin = "TPMin2.gif";
            var rows = document.getElementById('<%= GridView1.ClientID %>').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
            var image = document.getElementsByName(imageName);
            var imageSrc = image[0].src;
            if (imageSrc.match(imageMax) != null) {
                image[0].src = imageMin;
                index++;
                while (continueLoop) {
                    if (rows[index].cells[1].innerHTML == "") {
                        rows[index].style.display = "block";
                        index++;
                        if (index >= rows.length) {
                            continueLoop = false;
                        }
                    }
                    else {
                        continueLoop = false;
                    }
                }
            }
            else {
                image[0].src = imageMax;
                index++
                while (continueLoop) {
                    if (rows[index].cells[1].innerHTML == "") {
                        rows[index].style.display = "none";
                        index++;
                        if (index >= rows.length) {
                            continueLoop = false;
                        }
                    }
                    else {
                        continueLoop = false;
                    }
                }
            }
        }

        function ExpandCollapseAll(linkId) {
            var link = document.getElementById(linkId);
            var imageMax = "TPMax2.gif";
            var imageMin = "TPMin2.gif";
            var rows = document.getElementById('<%= GridView1.ClientID %>').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
            if (link.innerHTML == 'Expand All') {
                link.innerHTML = 'Collapse All';
                for (var i = 1; i < rows.length; i++) {
                    if (rows[i].cells[1].innerHTML != "") {
                        if (rows[i].cells[0].childNodes[0] != undefined) {
                            var img = rows[i].cells[0].childNodes[0];
                            img.src = imageMin;
                        }
                    }
                    else {
                        rows[i].style.display = "block";
                    }
                }
            }
            else {
                link.innerHTML = 'Expand All';
                for (var i = 1; i < rows.length; i++) {
                    if (rows[i].cells[1].innerHTML != "") {
                        if (rows[i].cells[0].childNodes[0] != undefined) {
                            var img = rows[i].cells[0].childNodes[0];
                            img.src = imageMax;
                        }
                    }
                    else {
                        rows[i].style.display = "none";
                    }
                }
            }
        }
    </script>

</head>
<body>
    <form id="form1" runat="server">
    <div>
        <div onclick="javascript:ExpandCollapseAll(this.id);" id="CollapseExpandAllLink"
            style="color: Blue; text-decoration: underline; cursor: pointer" runat="server">
            Collapse All</div>
        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" OnRowDataBound="GridView1_OnRowDataBound"
            OnDataBound="GridView1_DataBound">
            <Columns>
                <asp:ImageField DataImageUrlField="ImageUrl">
                </asp:ImageField>
                <asp:BoundField DataField="Company" HeaderText="Company" />
                <asp:BoundField DataField="FirstName" HeaderText="First Name" />
                <asp:BoundField DataField="LastName" HeaderText="Last Name" />
            </Columns>
        </asp:GridView>
    </div>
    </form>
</body>
</html>

ASPX Code Behind

using System;
using System.Collections.Generic;
using System.Data;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page
{
    public string PreviousGroupingValue { get; set; }
    public string ImageMin = "TPMin2.gif";
    public string ImageMax = "TPMax2.gif";
    protected void Page_Load(object sender, EventArgs e)
    {       
        DataTable dt = new DataTable();
        dt.Columns.Add("ImageUrl");
        dt.Columns.Add("Company");
        dt.Columns.Add("FirstName");
        dt.Columns.Add("LastName");
        DataRow dr = dt.NewRow();
        foreach (Employee employee in GetEmployees())
        {
            dr = dt.NewRow();
            dr["ImageUrl"] = ImageMin;
            dr["Company"] = employee.Company;
            dr["FirstName"] = employee.FirstName;
            dr["LastName"] = employee.LastName;
            dt.Rows.Add(dr);
        }
        GridView1.DataSource = dt;
        GridView1.DataBind();
    }
    protected void GridView1_OnRowDataBound(object sender, GridViewRowEventArgs e)
    {
        string imageName = string.Empty;
        bool collapsedInitially = true;
        bool populateImageCtrl = false;
        if (collapsedInitially)
        {
            CollapseExpandAllLink.InnerText = "Expand All";
        }
        else
        {
            CollapseExpandAllLink.InnerText = "Collapse All";
        }
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            imageName = "ImageExpand" + e.Row.RowIndex.ToString();
            if (e.Row.RowIndex != 0)
            {
                if (GridView1.Rows[e.Row.RowIndex - 1].RowType == DataControlRowType.DataRow)
                {
                    if (e.Row.Cells[1].Text == PreviousGroupingValue)
                    {
                        PreviousGroupingValue = e.Row.Cells[1].Text;
                        e.Row.Cells[1].Text = "";
                        ((Image)e.Row.Cells[0].Controls[0]).Visible = false;
                        if (collapsedInitially)
                        {
                            e.Row.Style.Add("display", "none");
                        }
                    }
                    else
                    {
                        ((Image)GridView1.Rows[e.Row.RowIndex - 1].Cells[0].Controls[0]).Visible = false;
                        populateImageCtrl = true;
                    }
                }
            }
            else
            {
                populateImageCtrl = true;
            }
            if (populateImageCtrl)
            {
                ((Image)e.Row.Cells[0].Controls[0]).Attributes.Add("name", imageName);
                ((Image)e.Row.Cells[0].Controls[0]).Attributes.Add("onclick", "javascript:ExpandCollapse(" + (Convert.ToInt32(e.Row.RowIndex + 1)).ToString() + ", '" + imageName + "');");
                ((Image)e.Row.Cells[0].Controls[0]).ImageUrl = ImageMax;
                PreviousGroupingValue = e.Row.Cells[1].Text;
            }
        }
    }

    protected void GridView1_DataBound(object sender, EventArgs e)
    {
        if (GridView1.Rows.Count > 1)
        {
            if (GridView1.Rows[GridView1.Rows.Count - 1].Cells[1].Text != GridView1.Rows[GridView1.Rows.Count - 2].Cells[1].Text)
            {
                ((Image)GridView1.Rows[GridView1.Rows.Count - 1].Cells[0].Controls[0]).Visible = false;
            }
        }
    }
    public List<Employee> GetEmployees()
    {
        List<Employee> employees = new List<Employee>();
        employees.Add(new Employee { Company = "C Company", FirstName = "Helen", LastName = "Hamaguchi" });
        employees.Add(new Employee { Company = "A Company", FirstName = "Ira", LastName = "Ivanovich" });
        employees.Add(new Employee { Company = "D Company", FirstName = "Greg", LastName = "Good" });
        employees.Add(new Employee { Company = "F Company", FirstName = "Adam", LastName = "Apple" });       
        employees.Add(new Employee { Company = "F Company", FirstName = "Carl", LastName = "Castle" });
        employees.Add(new Employee { Company = "D Company", FirstName = "Delia", LastName = "Dunken" });      
        employees.Add(new Employee { Company = "D Company", FirstName = "Francis", LastName = "Frantz" });      
        employees.Add(new Employee { Company = "E Company", FirstName = "Katsuhiro", LastName = "Kawasaki" });
        employees.Add(new Employee { Company = "D Company", FirstName = "Evan", LastName = "Erlich" });
        employees.Add(new Employee { Company = "E Company", FirstName = "Leslie", LastName = "Lang" });
        employees.Add(new Employee { Company = "F Company", FirstName = "Beverly", LastName = "Beard" });
        employees.Add(new Employee { Company = "E Company", FirstName = "Marvin", LastName = "Marx" });
        employees.Add(new Employee { Company = "Z Company", FirstName = "Nelson", LastName = "Nellis" });
        employees.Add(new Employee { Company = "E Company", FirstName = "Jasmine", LastName = "Jobs" });
        employees.Sort(delegate(Employee e1, Employee e2) { return e1.Company.CompareTo(e2.Company); });
        return employees;
    }
}

public class Employee
{
    public string Company { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

No comments:

Post a Comment