Click here to Skip to main content
15,891,473 members
Articles / Web Development / ASP.NET

Expandable panel inside a GridView

Rate me:
Please Sign up or sign in to vote.
4.82/5 (48 votes)
28 Sep 2008CPOL3 min read 302.6K   12.6K   204  
Another way to expand a detail panel inside a GridView.
/* ----------------------------------------------------------------------------
 *  Project ExpandPanelGridView
 *  Sample C# code to show details in an expandable panel inside a GridView
 * 
 *  Written by Amauri Rodrigues - September 2008
 *  email : rramauri@hotmail.com
 * 
 *  Feel free to modify this peace of code - It is a sample.
 *  Feel free to use in commercial and non-commercial projects.
 *  
 *  Requires Microsoft Ajax (http://www.asp.net/ajax)
 * 
 *  If you want to run this sample with data from AdventureWorks database :
 *  1) Download from http: http://www.codeplex.com/MSFTDBProdSamples
 *  2) Modify connectionString in Web.config :
 *     <connectionStrings>
 *       <add name="AdventureWorksConnectionString" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=AdventureWorks;User ID=sa"
 *         providerName="System.Data.SqlClient" />
 *     </connectionStrings>
 * ----------------------------------------------------------------------------
 */

/*
 * UpdateProgress "loading.gif"
 * http://www.ajaxload.info/
 * Type             : Bar
 * Background color : FFFFFF
 * Foreground color : 7BA100
 */

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Text;
using System.Globalization;

namespace ExpandPanelGridView
{
    /// <summary>
    /// Sample page to show a grid with details.
    /// <para>The grid will show the top stores.</para>
    /// <pata>The detail table will show the top models sold by one store.</pata>
    /// </summary>
    public partial class Default : System.Web.UI.Page
    {

        /// <summary>
        /// Collection of top stores
        /// </summary>
        private Bll.TopStore _TopStore;

        /// <summary>
        /// Number of visible columns
        /// </summary>
        private int _VisibleColumns = -1;

        /// <summary>
        /// Stores the identifications of buttons.
        /// <para>Will be used to enable/disable all buttons.</para>
        /// </summary>
        System.Collections.Generic.LinkedList<String> _Buttons;

        /// <summary>
        /// Page load.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e) {

            PrepareExpandableLines();

            radSimulatedData.Visible = false;
            radSqlData.Visible = false;
        }

        /// <summary>
        /// Releases allocated memory
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_UnLoad(object sender, EventArgs e) {

            if (_TopStore != null) {
                _TopStore.Dispose();
                _TopStore = null;
            }
        }

        /// <summary>
        /// Register the script to expand/contract details in order to show models
        /// </summary>
        private void PrepareExpandableLines() {

            // First, handle some specifics from Firefox
            // Reference : IE/Firefox show/hide/colspan
            //             http://forums.codewalkers.com/client-side-things-82/ie-firefox-show-hide-colspan-902435.html

            string StyleDisplayBlock = "block";

            if (this.Request.Browser.Browser.ToLower() == "firefox") {
                StyleDisplayBlock = "table-row";
            }

            string js = String.Format(CultureInfo.InvariantCulture,
                "var StyleDisplayBlock = '{0}';\n",
                StyleDisplayBlock);

            ScriptManager.RegisterClientScriptBlock(this.Page, this.Page.GetType(), "StyleDisplayBlock", js, true);

            // Then, include the script

            ScriptManager.RegisterClientScriptInclude(
                this.Page,
                this.Page.GetType(),
                "expand_models",
                "script/ExpandPanel.js");
        }

        /// <summary>
        /// Select simulated or data base stored data
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void odsStores_Selecting(object sender, ObjectDataSourceSelectingEventArgs e) {

            if (e.ExecutingSelectCount) return;

            if (radSimulatedData.Checked)
                e.InputParameters["simulated"] = true;
            else
                e.InputParameters["simulated"] = false;
        }

        /// <summary>
        /// Instantiates the business logic layer object, in order to fill the main grid
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void odsStores_ObjectCreating(object sender, ObjectDataSourceEventArgs e) {
            
            try {
                _TopStore = new Bll.TopStore();

                e.ObjectInstance = _TopStore;
            }
            catch (System.Exception ex) {

                // TODO: Error LOG

                ShowErrorMessage();
            }
        }

        /// <summary>
        /// This method is called when the object odsStores releases memory used by the business logic layer object.
        /// <para>If some error was found when reading from database, this method will show the error message.</para>
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void odsStores_ObjectDisposing(object sender, ObjectDataSourceDisposingEventArgs e) {

            Bll.TopStore topStore = e.ObjectInstance as Bll.TopStore;

            if (topStore != null && topStore.Error) {
                ShowErrorMessage();
                gridStores.Visible = false;
            }

            e.Cancel = true;  // topStore will be used in gridStores_RowDataBound
        }

        /// <summary>
        /// Called for each row of the main grid, calls a method to prepare the details
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void gridStores_RowDataBound(object sender, GridViewRowEventArgs e) {

            if (e.Row.RowType != DataControlRowType.DataRow)
                return;

            try {
                BO.Store storeRow = e.Row.DataItem as BO.Store;

                InsertModelsRow(e.Row, storeRow);
            }
            catch (System.Exception ex) {
                // TODO: Error LOG

                ShowErrorMessage();
            }
        }

        /// <summary>
        /// Called after the main grid was bound
        /// <para>Will create a Javascrip array, used to enable/disable the buttons</para>
        /// <para>See function EnableExpandButtons in ExpandPanel.js</para>
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void gridStores_DataBound(object sender, EventArgs e) {

            if (_Buttons != null && _Buttons.Count > 0) {

                foreach (string element in _Buttons) {

                    ScriptManager.RegisterArrayDeclaration(
                        this.Page,
                        "ExpandButtons", // The array's name
                        String.Format(CultureInfo.InvariantCulture, "'{0}'", element));  // The button ID
                }
            }
        }

        /// <summary>
        /// Prepares to show the top models for one store.
        /// <para>A new hidden row is inserted below the current row in the grid.</para>
        /// <para>That row will have only one cell, spanned to the entire row.</para>
        /// <para>Inside the new row, a panel is inserted as a simple HTML 'fieldset', just to show a border and some header.</para>
        /// <para>Inside the fieldset, a new panel is inserted. That panel will be filled with details when the user click the button.</para>
        /// </summary>
        /// <param name="gridViewRow">One row from the main grid</param>
        /// <param name="storeRow">One store</param>
        private void InsertModelsRow(GridViewRow gridViewRow, BO.Store storeRow) {

            // Gets the index for the new row

            Table htmlTab = gridStores.Controls[0] as Table;

            int newRowIndex = htmlTab.Rows.GetRowIndex(gridViewRow)+1;

            // Creates two panels : external and internal

            // External panel : A simple <fieldset>
            System.Web.UI.WebControls.Panel externalPanel = new Panel();
            externalPanel.EnableViewState = false;
            externalPanel.GroupingText = "Top models";
            externalPanel.ID = String.Format(CultureInfo.InvariantCulture, "pModelE_{0}", newRowIndex);
            externalPanel.CssClass = "expand-painel";

            // Internal panel : A <div> as a container for top models
            System.Web.UI.WebControls.Panel internalPanel = new Panel();
            internalPanel.EnableViewState = false;
            internalPanel.ID = String.Format(CultureInfo.InvariantCulture, "pModel_{0}", newRowIndex);

            externalPanel.Controls.Add(internalPanel);

            // Inserts the panels in one cell

            TableCell cell = new TableCell();
            //cell.HorizontalAlign = HorizontalAlign.Left;
            cell.BorderStyle = BorderStyle.None;
            cell.Controls.Add(externalPanel);
            cell.ColumnSpan = VisibleColumns;

            // Inserts the cell in a collection of cell (only one cell)

            TableCell[] cells = new TableCell[1];

            cells[0] = cell;

            // Inserts one row under the current row

            GridViewRow newRow = new GridViewRow(
                newRowIndex,
                newRowIndex,
                DataControlRowType.DataRow,
                DataControlRowState.Normal);

            newRow.ID = String.Format(CultureInfo.InvariantCulture, "linM_{0}", newRowIndex);

            newRow.Style.Add(HtmlTextWriterStyle.Display, "none");

            // Inserts the cell in the new row

            newRow.Cells.AddRange(cells);

            // Inserts the new row in the controls collection of the table (grid)

            htmlTab.Controls.AddAt(newRowIndex, newRow);

            // contextKey = CustomerId + radSimulatedData.Checked, separated by '-'
            string contextKey = String.Format(CultureInfo.InvariantCulture,
                "{0}-{1}",
                storeRow.CustomerId,
                (radSimulatedData.Checked ? 1 : 0));

            // Locates the button and prepares the 'onclick' event

            Image btn = gridViewRow.FindControl("imgModel") as Image;

            btn.Attributes["onClick"] = String.Format(CultureInfo.InvariantCulture,
                "ExpandModels('{0}', '{1}', '{2}', '{3}');",
                contextKey,
                newRow.ClientID,
                internalPanel.ClientID,
                btn.ClientID);

            // Insert the ID into the buttons list

            if (_Buttons == null)
                _Buttons = new System.Collections.Generic.LinkedList<string>();

            _Buttons.AddLast(btn.ClientID);
        }

        /// <summary>
        /// Number of visible columns
        /// </summary>
        private int VisibleColumns {
            get {
                if (_VisibleColumns == -1) {
                    _VisibleColumns = 0;
                    for (int i = 0; i < gridStores.Columns.Count; i++) {
                        if (gridStores.Columns[i].Visible) _VisibleColumns++;
                    }
                }
                return _VisibleColumns;
            }
        }

        /// <summary>
        /// Shows a generic error message to the user
        /// </summary>
        private void ShowErrorMessage() {

            lblMsgErroTitulo.Text = "ERROR";
            lblMsgErro.Text = "Can not query the database";

            popupMsgErro.Show();
        }

        /// <summary>
        /// Builds a string to populate the expanded line with the models
        /// </summary>
        /// <param name="contextKey">CustomerId + radSimulatedData.Checked, separated by '-'</param>
        /// <returns>HTML table filled with top models</returns>
        [System.Web.Services.WebMethod]
        [System.Web.Script.Services.ScriptMethod]
        public static string ExpandModelsService(string contextKey) {

#if DEBUG
            // Small delay to provide time to show 'loading.gif'
            System.Threading.Thread.Sleep(2000);
#endif

            StringBuilder sb = new StringBuilder();

            System.Collections.ObjectModel.ReadOnlyCollection<BO.Model> topModels = null;

            // Extracts CustomerID and 'radSimulatedData.Checked' from the context key

            string[] keys = contextKey.Split('-');

            bool simulatedData = (keys[1][0] == '1');

            int customerId = 0;

            bool hasError = false;

            if (!Int32.TryParse(keys[0], out customerId)) {
                // TODO: Error LOG
                hasError = true;
                sb.Append("<span class=\"error-models\">ERROR : Can not get the top models</span>");
            }
            else {

                try {
                    topModels = Bll.TopModel.GetTopModels(customerId, simulatedData);
                }
                catch (System.Exception ex) {
                    // TODO: Error LOG
                    hasError = true;
                    sb.Append("<span class=\"error-models\">ERROR : Can not get the top models</span>");
                }

                if (!hasError) {

                    if (topModels == null || topModels.Count == 0) {

                        sb.Append("<span class=\"notfound-models\">Top models does not found for this store.</span>");                       
                    }
                    else {
                        sb.Append(PopulateDetailsTable(topModels));
                    }
                }
            }

            if (topModels != null) {
                topModels = null;
            }

            return sb.ToString(); ;
        }

        /// <summary>
        /// Populates a HTML table with the top models data
        /// </summary>
        /// <param name="topModels">Data about the top models</param>
        /// <returns>HTML table filled with top models</returns>
        private static string PopulateDetailsTable(System.Collections.ObjectModel.ReadOnlyCollection<BO.Model> topModels) {

            StringBuilder sb = new StringBuilder("<table class = 'tab-mod'> \n"
            + "<tr> \n"
            + "<th>Model</th> \n"
            + "<th>Qty</th> \n"
            + "<th>Total price</th> \n"
            + "<th>Last sale</th> \n"
            + "</tr> \n",
            topModels.Count * 180);

            foreach (BO.Model modelRow in topModels) {

                sb.Append("<tr> \n");

                sb.AppendFormat(CultureInfo.CurrentCulture,
                    "<td>{0}</td> ",
                    modelRow.Name);

                sb.AppendFormat(CultureInfo.CurrentCulture,
                    "<td class=\"cellright\">{0}</td> ",
                    modelRow.Qty);

                sb.AppendFormat(CultureInfo.CurrentCulture,
                    "<td class=\"cellright\">{0:n2}</td> ",
                    modelRow.TotalPrice);

                sb.AppendFormat(CultureInfo.CurrentCulture,
                    "<td class=\"cellcenter\">{0}</td> ",
                    modelRow.LastSale.ToShortDateString());

                sb.Append("</tr> \n");
            }

            sb.Append("</table> \n");

            return sb.ToString();
        }

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Ápex Logik D'ata
Brazil Brazil
Born and lives in São Paulo - Brasil.
Programmer since 1982, and in 'tailor' mode since 1995, I had the oportunity to work for many kinds of business.
Today I create Windows Forms applications, Web projects and Palm OS applications.
My experience :
Languages : Cobol, Fortran, Basic, Clipper, Turbo Pascal, Data Flex, C, C++, Turbo C++, Borland C++ Builder, C#, Javascript.
DBMS : Adabas, DBF, Paradox, MS Access, DB2, Oracle, MS SQL Server.
I Know and experienced a little : Natural, Prolog, Lisp, Assembly IBM/370, Assembly Z80, Assembly 8080, MS Visual Basic (5/6).

Comments and Discussions