JQuery AJAX Grid Group

Building jQuery and ASP.Net AJAX Enabled Controls, The jQueryCollapsiblePanelExtender Part 2 Server Control

by mosessaur| 27 October 2008| 8 Comments

In the previous part I talked about how to build the client control. In this part I'll show how to put it all together to build an ASP.NET AJAX Enabled Server Control. You can view the demo that demonstrate control usage here.

The good thing about ASP.NET AJAX is that it supports fully programmable interface for both Server and Client control. And make a connection between both control. So you just need to put the control declaration on the ASPX page and ASP.NET will make it work for you. On the other hand, you have full access to the client APIs so that you can do some manual calls to the client APIs as well.

Building the Server Control:

This is an ASP.NET AJAX Extender control. Means it extends existing ASP.NET control to enable ASP.NET AJAX on it. In this case this extender extends ASP.NET Panel control, that is why it is called CollapsiblePanelExtender. Here is an article to show you in details How To build Extender Controls.

Basically jQueryCollapsiblePanelExtender I am building here inherits directly from ExtenderControl. This required to implement 2 methods GetScriptDescriptors and GetScriptReferences which I am going to explore later. But now, I want to take your attention that I am using jQuery, and I wanted the developer to have the option to specify the path of the jQuery library. If he/she did not specify the path, the default path is used, which is the the one hosted by google at http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js. To do that I made a property and Call jQueryScriptPath that gets and sets jQuery javascript library path.

For each property I defined in the client library, there is a mapped property for it on the Server Side control. Same thing for events.

public string jQueryScriptPath
{
get { return (string)ViewState["jQueryScriptPath"]; }
set { ViewState["jQueryScriptPath"] = value; }
}

public string CollapseControlID
{
get { return (string)ViewState["CollapseControlID"]; }
set { ViewState["CollapseControlID"] = value; }
}
public string ExpandControlID
{
get { return (string)ViewState["ExpandControlID"]; }
set { ViewState["ExpandControlID"] = value; }
}
public string ImageControlID
{
get { return (string)ViewState["ImageControlID"]; }
set { ViewState["ImageControlID"] = value; }
}
public string ExpandedImage
{
get { return (string)ViewState["ExpandedImage"]; }
set { ViewState["ExpandedImage"] = value; }
}
public string CollapsedImage
{
get { return (string)ViewState["CollapsedImage"]; }
set { ViewState["CollapsedImage"] = value; }
}
public bool Collapsed
{
get { return (bool)(ViewState["Collapsed"] ?? false); }
set { ViewState["Collapsed"] = value; }
}
public bool SuppressPostBack
{
get { return (bool)(ViewState["SuppressPostBack"] ?? false); }
set { ViewState["SuppressPostBack"] = value; }
}

public string OnExpand
{
get { return (string)(ViewState["OnExpand"] ?? string.Empty); }
set { ViewState["OnExpand"] = value; }
}
public string OnExpanded
{
get { return (string)ViewState["OnExpanded"] ?? string.Empty; }
set { ViewState["OnExpanded"] = value; }
}
public string OnExpandComplete
{
get { return (string)ViewState["OnExpandComplete"] ?? string.Empty; }
set { ViewState["OnExpandComplete"] = value; }
}
public string OnCollapse
{
get { return (string)ViewState["OnCollapse"] ?? string.Empty; }
set { ViewState["OnCollapse"] = value; }
}
public string OnCollapsed
{
get { return (string)ViewState["OnCollapsed"] ?? string.Empty; }
set { ViewState["OnCollapsed"] = value; }
}
public string OnCollapseComplete
{
get { return (string)ViewState["OnCollapseComplete"] ?? string.Empty; }
set { ViewState["OnCollapseComplete"] = value; }
}

Maintaining Client State:

To maintain a client state, hidden field is used to store the current state of the CollapsiblePanel. I copied this idea from the AjaxControlToolkit. And actually it is totally make sense. This helps to maintain CollapsiblePanel State across Postbacks. It worth to mention that everything related to maintain client state is copied from AjaxControlToolkit base class ExtenderControlBase. Below is the code and it is comment for better explanation:

protected override void OnInit(EventArgs e)
{
//Create Client State Hidden Field and add to Controls collection
CreateClientStateField();
Page.PreLoad += Page_PreLoad;
base.OnInit(e);
}
protected override void OnLoad(EventArgs e)
{
if (!_loadedClientStateValues)
{
LoadClientStateValues();
}
base.OnLoad(e);
}
protected override void OnPreRender(EventArgs e)
{
//Must Call base class OnPreRender
base.OnPreRender(e);
//Saves Client State
SaveClientStateValues();
}
protected override void Render(HtmlTextWriter writer)
{
// Use ASP.NET's mechanism for ensuring Controls are within a form
// (since unexpected behavior can occur when ExtenderControls are not)
if (null != Page)
{
Page.VerifyRenderingInServerForm(this);
}
base.Render(writer);
}
private void Page_PreLoad(object sender, EventArgs e)
{
// Needs to happen sometime after ASP.NET populates the control
// values from the postback but sometime before Load so that
// the values will be available to controls then. PreLoad is
// therefore the obvious choice.
LoadClientStateValues();
}
/// <summary>
/// Creates or form ClientStateFiledID
/// </summary>
/// <returns>Client State Field Control ID</returns>
private string GetClientStateFieldID()
{
return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}_ClientState", ID);
}
/// <summary>
/// Creates HiddenField that is used to main client state
/// </summary>
/// <returns>ASP.NET HiddenField instance</returns>
private HiddenField CreateClientStateField()
{
// add a hidden field so we'll pick up the value
//
HiddenField field = new HiddenField();
field.ID = GetClientStateFieldID();
Controls.Add(field);
ClientStateFieldID = field.ID;
field.Value = this.Collapsed.ToString().ToLower();
return field;
}
/// <summary>
/// Loads client state and store it on ClientState Property.
/// It also fires ClientStateValuesLoaded if an event handler attached
/// </summary>
private void LoadClientStateValues()
{
if (!string.IsNullOrEmpty(ClientStateFieldID))
{
HiddenField hiddenField = (HiddenField)NamingContainer.FindControl(ClientStateFieldID);

if ((hiddenField != null) && !string.IsNullOrEmpty(hiddenField.Value))
{
ClientState = bool.Parse(hiddenField.Value);
}
}

if (null != ClientStateValuesLoaded)
{
ClientStateValuesLoaded(this, EventArgs.Empty);
}

_loadedClientStateValues = true;
}
/// <summary>
/// Saves Client State value into the HiddenField (ClientState Field)
/// </summary>
private void SaveClientStateValues()
{
HiddenField hiddenField = null;

// if we don't have a value here, this properties
// object may have been created dynamically in code
// so we create the field on demand.
if (string.IsNullOrEmpty(ClientStateFieldID))
{
hiddenField = CreateClientStateField();
}
else
{
hiddenField = (HiddenField)NamingContainer.FindControl(ClientStateFieldID);
}

if (hiddenField != null)
{
hiddenField.Value = ClientState.ToString().ToLower();
}
}

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool ClientState
{
get
{
return _clientState;
}
set
{
_clientState = value;
}
}
Implementing GetScriptDescriptors and GetScriptReferences:

GetScriptDescriptors Registers the ScriptDescriptor objects for the control. That would include properties and events. Just as the following:

protected override IEnumerable<ScriptDescriptor>
GetScriptDescriptors(System.Web.UI.Control targetControl)
{
ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender", targetControl.ClientID);

descriptor.AddProperty("ClientStateFieldID", this.ClientStateFieldID);
descriptor.AddProperty("CollapseControlID", this.CollapseControlID);
descriptor.AddProperty("ExpandControlID", this.ExpandControlID);
descriptor.AddProperty("ImageControlID", this.ImageControlID);
descriptor.AddProperty("ExpandedImage", Page.ResolveClientUrl(this.ExpandedImage));
descriptor.AddProperty("CollapsedImage", Page.ResolveClientUrl(this.CollapsedImage));
descriptor.AddProperty("Collapsed", this.Collapsed);
descriptor.AddProperty("SuppressPostBack", this.SuppressPostBack);
if (!String.IsNullOrEmpty(this.OnExpand))
{
descriptor.AddEvent("expanding", this.OnExpand);
}
if (!String.IsNullOrEmpty(this.OnExpanded))
{
descriptor.AddEvent("expanded", this.OnExpanded);
}
if (!String.IsNullOrEmpty(this.OnExpandComplete))
{
descriptor.AddEvent("expandComplete", this.OnExpandComplete);
}
if (!String.IsNullOrEmpty(this.OnCollapse))
{
descriptor.AddEvent("collapsing", this.OnCollapse);
}
if (!String.IsNullOrEmpty(this.OnCollapsed))
{
descriptor.AddEvent("collapsed", this.OnCollapsed);
}
if (!String.IsNullOrEmpty(this.OnCollapseComplete))
{
descriptor.AddEvent("collapseComplete", this.OnCollapseComplete);
}
yield return descriptor;
}

While GetScriptReferences Registers the script libraries for the control. In this case the libraries would be the Client Library for this control I built in part 1 and the jQuery Library. The Client Control Library is an embedded resource while the jQuery Library is defined using jQueryScriptPath property:
protected override IEnumerable<ScriptReference>GetScriptReferences()
{
string jqueryPath = jquery126Live;
if (this.jQueryScriptPath != null)
{
jqueryPath = Page.ResolveClientUrl(this.jQueryScriptPath);
}

ScriptReference[] scripts =
{ new ScriptReference(jqueryPath),
new ScriptReference("jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.js",
this.GetType().Assembly.FullName) };
return scripts;
}
This was all about the the Extender control. Now I'll move on how to use it.

Live Demo:

Below is the ASPX markup for the control usage. As you'll notice the usage is exactly the same as the original CollapsiblePanel:

  1. <cc1:jQueryCollapsiblePanelExtender ID="collapsiblePanelEx"   
  2.     TargetControlID="childPanel"  
  3.     CollapseControlID="parentLink"   
  4.     ExpandControlID="parentPanel"   
  5.     ImageControlID="imgExpandCollapse"  
  6.     CollapsedImage="~/images/plus.png"  
  7.     ExpandedImage="~/images/minus.png"   
  8.     Collapsed="false"  
  9.     OnCollapsed="collapsed"   
  10.     OnExpanded="expanded"   
  11.     runat="server"/>  
<cc1:jQueryCollapsiblePanelExtender ID="collapsiblePanelEx" 
    TargetControlID="childPanel"
    CollapseControlID="parentLink" 
    ExpandControlID="parentPanel" 
    ImageControlID="imgExpandCollapse"
    CollapsedImage="~/images/plus.png"
    ExpandedImage="~/images/minus.png" 
    Collapsed="false"
    OnCollapsed="collapsed" 
    OnExpanded="expanded" 
    runat="server"/>

The demo I am going to present here is an old demo. But this time I'm using jQueryCollapsiblePanelExtender instead of the Original CollapsiblePanelExtender. Note that I am handling OnExpand/expanding client event to initiate AJAX request using PageMethod call, just like as I did in the old demo. Download the sample project to review the code and explore the control's code.

Related posts

Building jQuery and ASP.Net AJAX Enabled Controls, The jQueryCollapsiblePanelExtender Part 1 Client ControlAt the end of the last month (September 2008) Microsoft announced that it will be shipping jQuery wi...Building on demand Master/Detail grouping Grid with GridView and ASP.NET AJAX toolkit CollapsiblePanelExtenderIntroduction Earlier this year Matt Berseth posted about Building a Grouping Grid with the ASP.NET ...Building GridView With ASP.NET AJAX Enabled Control with Script#Introduction: I was thinking of building ASP.NET AJAX Supported controls inorder to practice both AS...
Comments