At the end of the last month (September 2008) Microsoft announced that it will be shipping jQuery with Visual Studio going forward. It was a great news for all ASP.NET developers who are jQuery fans as well as jQuery and ASP.NET AJAX folks like me. Before and after that, there were many posts made around the same subject. It will take a full post to list all these posts. But it would be easy for me to list ASP.NET jQuery heroes around such Rick Strahl, Dave Ward and Matt Berseth. There are many other heroes around such as Bill Beckelman who made a great collection of ASP.NET with jQuery Demos.
For specific posts around ASP.NET AJAX and jQuery I recommend to refer to the following posts by Dave Ward's blog:
Introduction:
We all heard about and maybe worked with ASP.NET AjaxControlToolkit! A set of wonderful controls, but they are heavy, maybe not all of them but many of of them. Beside they require lots of script files which increase the response size.
I was thinking of building jQuery UI Widgets to clone some of AjaxControlToolkit controls. And after Microsoft announcement about jQuery I had another idea! I always liked how ASP.NET AJAX component model, both client and server models. It is easy to build ASP.NET AJAX Enabled Controls especially for ASP.NET control developers. ASP.NET AJAX client and server architecture really ease the development ASP.NET AJAX Enabled Controls and also the core ASP.NET AJAX client library has no massive performance issues, and it is widely used.
On the other hand, I loved the ease of using jQuery as well, how fancy it provides UI Effects and manipulation of DOM. Many many things I wished to have in ASP.NET AJAX were exist in jQuery. So I though of building an ASP.NET AJAX Enabled Control to clone the existing AjaxControlToolkit's control CollapsiblePanelExtender. And use ASP.NET AJAX client and server component model while using jQuery for UI Effects. In this case it would be collapsing (Slide Up) and expanding (Slide Down). [View Demo]
Prerequisites:
You must be familiar with both ASP.NET AJAX and jQuery before you continue on this post. I assume that you already know how to build ASP.NET AJAX Enabled Controls (Controls & Extenders). I assume that you familiar with ASP.NET AJAX client library.
Building the Client Component:
As this going to be based on the existing CollapsiblePanelExtender, I decided to have a look at the existing client component. And I end up with Copy/Paste behaviour. I noticed the that the main issue with the CollapsiblePanelExtender is the animation, and the inclosure of the animation script. The animation and effects provided by jQuery is much more faster and better than the ones provided by the AjaxControlToolkit. So, I made a clone and removed everything related to animation.
I will call this control jQueryCollapsiblePanelExtender. I'm not going to support all features of CollapsiblePanelExtender, but will support the core features. And everything else can be implemented later.
I'll start with declaring and registering the client component namespace and class. After creating an ASP.NET AJAX Server Control Extender Project a JavaScript file will be added to the project. This is where I will code my client component:
/// <reference name="MicrosoftAjax.js"/>
/// <reference path="jquery-1.2.6.min.js" name="jquery-1.2.6.min.js"/>
Type.registerNamespace("jQueryASPNetAjaxControls");
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender = function(element) {
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.initializeBase(this, [element]);
}
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.prototype = {
initialize: function() {
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.callBaseMethod(this, 'initialize');
//Initialize Code
},
dispose: function() {
//Dispose Code
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.callBaseMethod(this, 'dispose');
}
//Methods and Properties (Getters and Setters)
};
//Class Registration
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.registerClass('jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender', Sys.UI.Behavior);
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
Line 7 is the
initialize function, where I will initialize the extender and attache even handlers to do collapse and expand operations. Line 12 is
dispose function, where I will clean up my code like remove all event handlers. on Line 19 this is client component class registration. Note that the base class is
Sys.UI.Behavior. It is true that I said I made copy and paste from
CollapsiblePanelExtender, but also I made major modifications. I do not use
AjaxControlToolkit server or client libraries. I am using only the Core ASP.NET AJAX Library.
Client API Properties:
Based on CollapsiblePanelExtender few properties will be needed such as:
- ExpandControlID/CollapseControlID: The controls that will expand or collapse the panel on a click, respectively. If these values are the same, the panel will automatically toggle its state on each click
- Collapsed: Specifies that the object should initially be collapsed or expanded.
- ImageControlID: The ID of an Image control where an icon indicating the collapsed status of the panel will be placed. The extender will replace the source of this Image with the CollapsedImage and ExpandedImage URLs as appropriate.
- CollapsedImage: The path to an image used by ImageControlID when the panel is collapsed.
- ExpandedImage - The path to an image used by ImageControlID when the panel is expanded.
These properties are going to have their values set when the client control is created. And this is the job of the Server Control as I will show in next part.
Few variables were needed and were created on the client class constructor:
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender = function(element) {
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.initializeBase(this, [element]);
this._$element = null;
this._$expandControl = null;
this._$collapseControl = null;
this._$imageControl = null;
this._$clientStateControl = null;
this._expandControlID = null;
this._collapseControlID = null;
this._imageControlID = null;
this._clientStateFieldID = null;
this._expandedImage = null;
this._collapsedImage = null;
this._collapseClickHandler = null;
this._expandClickHandler = null;
this._suppressPostBack = null;
this._collapsed = false;
}
All variable that is have _$ prefix are going to be jQuery objects. Below is the client getter and setter functions for one of the above mentioned properties:
get_CollapseControlID: function() {
return this._collapseControlID;
},
set_CollapseControlID: function(value) {
if (this._collapseControlID != value) {
this._collapseControlID = value;
this._$collapseControl = $('#' + value);
this.raisePropertyChanged('CollapseControlID');
}
}
On line 7 I am creating a jQuery object around the Collapse Control. And I also as shown below made a getter method that return this jQuery object, to be part of the component client APIs if needed:
get_CollapsejQObj: function() {
return this._$collapseControl;
}
All properties are implemented on client APIs as the above one.
Client API Events:
Again, the CollapsiblePanelExtender support client event handling for some events such expanded and collapsed events. And part of the core features these events are needed. Nothing new here it is just as it is in the original CollapsiblePanelExtender:
add_collapsed: function(handler) {
this.get_events().addHandler('collapsed', handler);
},
remove_collapsed: function(handler) {
this.get_events().removeHandler('collapsed', handler);
},
raiseCollapsed: function(eventArgs) {
var handler = this.get_events().getHandler('collapsed');
if (handler) {
handler(this, eventArgs);
}
}
Initializing Client Component:
During initialization process, previous state (Expanded or Collapsed) of the CollapsiblePanel is maintained, so in case of PostBack if the Panel is collapsed it will remain like that, and if expanded it will also remain like that. Also the CollapsiblePanel is being setup, for example showing expand or collapse image. In original CollapsiblePanel it also set the appropriate collapse or expand text on a predefined label as well as images title. Finally registration of event handlers is made. Below is the code for the initialize function:
initialize: function() {
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.callBaseMethod(this, 'initialize');
var element = this.get_element();
//Creating jQuery Object for the Target Element.
this._$element = $(this.get_element());
//Maintaining Client State.
var lastState = this.get_ClientState();
if (lastState && lastState != "") {
var wasCollapsed = Boolean.parse(lastState);
if (this._collapsed != wasCollapsed) {
this._collapsed = wasCollapsed;
this.raisePropertyChanged('Collapsed');
}
}
if (this._collapsed) {
$(element).hide();
}
else {
$(element).show();
}
//Setup CollapsiblePanel State (Set approperiate Collapse\Expand image)
this._setupState(this._collapsed);
//Initializing event handlers
if (this._collapseControlID == this._expandControlID) {
//if the collapse cdontrol is th esame as the expand one,
//togglePanel would the handler.
this._collapseClickHandler = Function.createDelegate(this, this.togglePanel);
this._expandClickHandler = null;
} else {
//else each control would have a separate handler
this._collapseClickHandler = Function.createDelegate(this, this.collapsePanel);
this._expandClickHandler = Function.createDelegate(this, this.expandPanel);
}
if (this._collapseControlID) {
var collapseElement = $get(this._collapseControlID);
if (!collapseElement) {
throw Error.argument('CollapseControlID', this._collapseControlID);
} else {
//register handler for click event on the collapse control
$addHandler(collapseElement, 'click', this._collapseClickHandler);
}
}
if (this._expandControlID) {
if (this._expandClickHandler) {
var expandElement = $get(this._expandControlID);
if (!expandElement) {
throw Error.argument('ExpandControlID', this._expandControlID);
} else {
//register handler for click event on the expand control
$addHandler(expandElement, 'click', this._expandClickHandler);
}
}
}
}
Disposing:
Disposing is very important. Having events registered they must be cleared and removed on disposing. And that is what dispose function do as shown below:
dispose: function() {
var element = this.get_element();
if (this._collapseClickHandler) {
var collapseElement = (this._collapseControlID ? $get(this._collapseControlID) : null);
if (collapseElement) {
$removeHandler(collapseElement, 'click', this._collapseClickHandler);
}
this._collapseClickHandler = null;
}
if (this._expandClickHandler) {
var expandElement = (this._expandControlID ? $get(this._expandControlID) : null);
if (expandElement) {
$removeHandler(expandElement, 'click', this._expandClickHandler);
}
this._expandClickHandler = null;
}
jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender.callBaseMethod(this, 'dispose');
}
Expand\Collapse the UI Effects:
You might notice the following functions when registering event handlers: togglePanel, expandPanel and collapsePanel. All are public functions for the client API and they call other private functions that is responsible for doing the UI effect of collapsing or expanding. Below I'll show the code responsible for collapse and expand.
The private function responsible for the collapse shown below:
_doClose: function(eventObj) {
var eventArgs = new Sys.CancelEventArgs();
this.raiseCollapsing(eventArgs);
//Check if cancelling the operation
if (eventArgs.get_cancel()) {
return;
}
//Create a callback with context referencing the current component.
//The callback function is used to raise collapseComplete and collapsed events
var closedCallback = Function.createCallback(this.__closed, this);
//Collapse Effect with closedCallback function passed as parameter
this._$element.slideUp('normal', closedCallback);
//Perform setup for collapse state.
this._setupState(true);
if (this._suppressPostBack) {
if (eventObj && eventObj.preventDefault) {
eventObj.preventDefault();
} else {
if (event) {
event.returnValue = false;
}
return false;
}
}
}
On line 11, a Callback is created. Actually this is a tricky step. I wanted to raise CollapseCompleted and Collapsed events just after the collapse animation effect take place. The
jQuery slideUp and
slideDown fcunctions accept compete function callback as parameter. On the callback function, I need to access the client component to call
raiseXxx functions such as
raiseCollapsed to raise the required events. And this would require to made a call like this
this.raiseCollapsed(...);. But this is possible sense "
this" will refer to the expanded or collapsed DOM element. Thanks to
createCallback function, I was able to pass the component instance as a
context parameter. Below is the code for __closed private function that is used as the callback for
slideUp complete:
__closed: function(context) {
//this : refers to the collapsed element.
context.raiseCollapseComplete();
context.raiseCollapsed(Sys.EventArgs.Empty);
}
_doOpen function is exactly the same as
_doClose except it calls
slideDown function. Comparing this code with original CollapsiblePanel code, the UI Effect is done with one line of code using
jQuery, while using ASP.NET AJAX Animation script it is another story. It require to initialize animation object and calls stop/play methods when appropriate. Of course the original CollapsiblePanel has a feature for specifying collapse and expand size. But these settings can be easily applied using
jQuery animate function with less code as well.
Live Demo
As I am going to discuss the Server Extender Control in next part, I will show how to use this client control through manual JavaScript coding. Basically it is required to create the client control using $create shortcut function on client application init event just as below:
Sys.Application.add_init(function() {
var c = $create(jQueryASPNetAjaxControls.jQueryCollapsiblePanelExtender,
{
ClientStateFieldID: 'parentPanelState',
CollapseControlID: 'parentPanel',
ExpandControlID: 'parentPanel',
ImageControlID: 'imgExpandCollapse',
ExpandedImage: 'images/minus.png',
CollapsedImage: 'images/plus.png',
Collapsed: true
},
null, null, $get("childPanel"));
});
Just a small note, in line 4, this ClientStateFieldID is a hidden field that holds the current state of the CollapsiblePanel (expanded or collapsed). This is to maintain client state through PostBacks.
You can view a live demo here. Also download the sample web to explore the code.
Conclusion:
Many more features can be added to the CollapsiblePanel, such as expand direction (vertical, horizontal) and different animation style during collapse or expand as well as collapse and expand speed. I didn't demonstrate that here to save post space as well as leaving something for you to play with. Believe me using jQuery with Core ASP.NET AJAX library is much better than using AjaxControlToolkit. Not because AjaxControlToolkit is bad, but because it includes many scripts and its performance isn't that good. You can save hundreds of KBs by using jQuery and then build your own required controls with ASP.NET AJAX Core Library. For example if you replaced Animation script made in AjaxControlToolkit with Effects provided by jQuery not only you'll save bytes and get better UI Effects performance, but also you'll have fully featured framework which is easy to use with cost of 17 KB.
Hope you liked this post and see you in next part.