/* ----------------------------------------------------------------------------
* 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();
}
}
}