Building a Custom AJAX Control with the Help of jQuery

jQuery and ASP.NET AJAX play nicely together

Brian Mains

November 27, 2009

14 Min Read
ITPro Today logo in a gray background | ITPro Today

jQuery, a very capable JavaScript library, can perform wide-ranging operations with a few simple statements. Many people have posted examples of its power on the Internet, executing complex tasks with a few lines of code or externally through the use of a plug-in. It’s through jQuery’s features and extendibility that jQuery continues to shine.

Some people may think that jQuery and ASP.NET AJAX clash, but the opposite is true: These two libraries play nicely together. Some script libraries are not compatible because they override each other’s statements. Although jQuery and most other libraries use the $ as the key way to invoke the library’s functions, ASP.NET AJAX doesn’t use the $ for any of its features. Rather, ASP.NET AJAX uses a CLR-like approach to architecting client-side solutions, which means it uses a namespaced approach. Also, ASP.NET AJAX’s extensions of existing JavaScript objects do not conflict with any jQuery extensions (excluding plug-ins). Embedding jQuery in ASP.NET AJAX components works nicely.

Because of this, you can easily integrate jQuery into ASP.NET AJAX controls and extenders. In this article, I’ll create a custom jQuery-based control using its drag and drop features. Dragging and dropping is a feature built into jQuery’s UI layer of scripts, available via a draggable script and a droppable script. Despite having separate scripts for each of these features, setting up dragging and dropping of elements is remarkably easy.

Primary Focus

Because the focus of this article is to examine an ASP.NET AJAX custom control that leverages jQuery, you’ll need at least a cursory knowledge of custom controls. I’ll try to explain key points, but I won’t be able to go into detail about everything related to this broad subject matter.

One of the primary objectives of AJAX controls is to make using AJAX easy. A .NET developer can drag and drop the server-side control, configure some of its settings, and the control will work without the developer having to know anything about JavaScript or AJAX. An AJAX control marries a server-side and a client-side class together to do the work. The server-side class has a way to talk to the client-side class, albeit not directly, and the client-side class can provide the AJAX and the rich functionality all developers and users ultimately want.

Let’s delve into the more complicated aspect of actually creating the control itself. This involves understanding the client-side structures that ASP.NET AJAX puts in place. The client-side portion of this control embeds jQuery drag and drop logic within a single control’s functionality, and manages all of jQuery’s related scripts. You do not need to implement or reference separate scripts; the control will handle all of this for you.

ASP.NET AJAX Controls

ASP.NET AJAX provides a way to create CLR-like objects in JavaScript. While this approach has been criticized, ASP.NET AJAX is, overall, easy to work with. ASP.NET AJAX doesn’t replace any of the existing JavaScript concepts, but instead enhances them. It still uses the concept of the constructor and prototype, adding new methods for the ability to create class, interface, or enumeration of specific structures. ASP.NET AJAX begins by defining classes using the structure shown in Figure 1.

Type.registerNamespace("Nucleo.Web.ContentControls");Nucleo.Web.ContentControls.DragDropPanelOperation =    function() { throw Error.invalidOperation(); }Nucleo.Web.ContentControls.DragDropPanelOperation.prototype ={Drag: 1,Drop: 2}Nucleo.Web.ContentControls.DragDropPanelOperation.registerEnum("Nucleo.Web.ContentControls.DragDropPanelOperation");Nucleo.Web.ContentControls.DragDropPanel = function(associatedElement) {Nucleo.Web.ContentControls.DragDropPanel.initializeBase(this, [associatedElement]);this._operation = null;}Nucleo.Web.ContentControls.DragDropPanel.prototype = { }Nucleo.Web.ContentControls.DragDropPanel.registerClass('Nucleo.Web.ContentControls.DragDropPanel', Nucleo.Web.BaseAjaxControl);

As you know, .NET uses namespaces to logically separate objects. In Figure 1, we have two objects: DragDropPanelOperation, which is an enumeration, and DragDropPanel, which is a class. The sole differentiation between the two object types is the corresponding method that is called: registerClass creates a class; registerEnum creates an enumeration. While both are actually classes, the differentiation lies in how ASP.NET AJAX views them. Internally, ASP.NET AJAX stores some metadata with the class and enumeration that signals the type of object being created.

No matter the type of object being created, a similar structure is used to create the objects. Working top-down, notice that the enumeration has a constructor, but doesn’t implement it: this is because an enumeration should never by directly instantiated. Instead, the prototype defines a name/value pair of enumerated values, separated by commas, defining a set of constant-like data like you see in C# or VB.NET. Classes work a little differently; classes implement their constructor, requiring that the base constructor be called (the purpose of the initializeBase method call). The prototype is blank at the moment, but this is normally defined with properties or methods.

All controls have to at least inherit from Sys.UI.Control; this is the base class for all client-side components. This base class has the client-side properties that you see from its server-side complement, which we’ll get to later.

Dragging and Dropping

The ASP.NET AJAX client-side class has to implement the prototype to specify some methods that we can use to enable drag or drop functionality, as well as enable our jQuery code. The central idea around this control is to render a panel and embed all of the drag and drop contents within the panel. An enumeration (seen in Figure 1) makes it easy to distinguish between the two operations while allowing the use of the same control to perform that function. The initialize method, the method that fires at the initialization of every control, seems like the perfect place to set up dragging and dropping, as shown in Figure 2.

Nucleo.Web.ContentControls.DragDropPanel.prototype = {get_operation: function() {return this._operation;},set_operation: function(value) {this._operation = value;},initialize: function() {Nucleo.Web.ContentControls.DragDropPanel.callBaseMethod(this, "initialize");if (jQuery) {var that = this;if (this.get_operation() == Nucleo.Web.ContentControls.DragDropPanelOperation.Drag) {jQuery(this.get_element()).draggable({start: function() { that._ondragStarting(Sys.EventArgs.Empty); },stop: function() { that._ondragEnding(Sys.EventArgs.Empty); }});}else if (this.get_operation() == Nucleo.Web.ContentControls.DragDropPanelOperation.Drop) {jQuery(this.get_element()).droppable({accept: function() { return true; },drop: function() { that._ondropped(Sys.EventArgs.Empty); }});}}}}

In Figure 2, the operation property stores the value of our enumeration (shown in Figure 1) to determine what type of operation the panel allows. Dragging or dropping is very simple in jQuery and is enabled via the draggable and droppable methods, which take a configurable list of properties. Notice that each setting for the drag and drop operations in Figure 2 defines a callback method, using the function() \{ \} notation. While these settings use callbacks, other settings use other values such as Booleans or strings.

The callbacks defined are called when a specific event occurs (drag starting, stopped). Within the callback method, the “this” pointer may refer to another object (via a closure) that reconfigures the “this” reference to point to something else other than the DragDropPanel control. Creating a “that” variable to point to the current instance of “this” is a way to retain the class reference to DragDropPanel. This way, the callback functions can call class methods with ease. This is important because these jQuery events bubble up to DragDropPanel events, as we’ll see in the next section.

The only setting that’s different is the accept method; the drop panel is used to determine the types of elements to allow or deny in dragging mode. By returning true, any type of element is allowed.

Notice the use of jQuery object directly; jQuery uses both the jQuery object and the $ notation; I use the jQuery variable directly in case the $ happens to be taken by another library. There is a safe way to handle this; jQuery also supports a noConflict() method, and when called it reverts the $ notation back to the original library that defined it. This is needed because most libraries compete for the $ notation.

Events

The other aspect to AJAX components is the events that they fire. By their very nature, events are a part of any type of application. When a user clicks on an HTML element, it fires a click event. This is one aspect of events, where an event is responsible from some user interaction. Events also can be a conditional change: they can fire whenever something within a JavaScript component changes—for instance, when a property A changes its value from 1 to 2, which corresponds to some event call.

Imagine a web form with a button in it. When a user clicks the button, the page posts back. This is due to user interaction (user mouse click) that causes an event (click) to fire on the server via the postback. This is one type of event. However, server controls, as they process logic on the server side, have their own events that fire based on a certain condition that is processed when a value changes on the server (the page lifecycle events are one example, as well as an ObjectDataSource control firing its Selecting and Selected events in response to a request from the control bound to its data).

This latter example is made available in ASP.NET AJAX via the events property (accessible through get_events getter). The events object is of type Sys.EventHandlerList and is used to store any handler for an event. Naturally, more than one event handler can be added.

This component has three events: dragStarted, dragEnded, and dropped, as Figure 3 shows. Essentially, these events bubble up the jQuery events I mentioned earlier. jQuery raises an event, say when the drag started, which calls DragDropPanel’s _ondragStarted method using the "that" trick I mentioned earlier, which in turn uses ASP.NET AJAX to fire a dragStarted event and notify its subscribers that dragging has started. How that occurs is handled by the Sys.EventHandlerList object and the ASP.NET AJAX framework; you do not have to worry about the particular details. Some client-side function (the event handler) will receive an event from the AJAX component and not from jQuery directly.

Nucleo.Web.ContentControls.DragDropPanel.prototype ={add_dragEnding: function(h) {this.get_events().addHandler("dragEnding", h);},remove_dragEnding: function(h) {this.get_events().removeHandler("dragEnding", h);},_ondragEnding: function(e) {var h = this.get_events().getHandler("dragEnding");if (h) h(this, e);},add_dragStarting: function(h) {this.get_events().addHandler("dragStarting", h);},remove_dragStarting: function(h) {this.get_events().removeHandler("dragStarting", h);},_ondragStarting: function(e) {var h = this.get_events().getHandler("dragStarting");if (h) h(this, e);},add_dropped: function(h) {this.get_events().addHandler("dropped", h);},remove_dropped: function(h) {this.get_events().removeHandler("dropped", h);},_ondropped: function(e) {var h = this.get_events().getHandler("dropped");if (h) h(this, e);},}

A method to handle one of these AJAX events would look like the one below:

function DragStarted(sender, e) \{//Code Here\}

Server-Side Components

As I mentioned before, ASP.NET AJAX marries the client-side component seen above to a server-side equivalent control, through a process of script description and registration. The description process specifies the initial values to pass from the server to the client, while the registration process specifies the client-side JavaScripts that the component will need.

On the server side, controls inherit from System.Web.UI.ScriptControl. This requires GetScriptDescriptors and GetScriptReferences methods, which handle this script description and referencing process. Take a look at the component in Figure 4.

public class DragDropPanel : ScriptControl{private ITemplate _contentTemplate = null;[PersistenceMode(PersistenceMode.InnerProperty),DesignerSerializationVisibility(DesignerSerializationVisibility.Content),MergableProperty(false),TemplateInstance(TemplateInstance.Single)]public ITemplate ContentTemplate{get { return _contentTemplate; }set { _contentTemplate = value; }}public string OnClientDragEnding{get { return (string)ViewState[“OnClientDragEnding”]; }set { ViewState[“OnClientDragEnding”] = value; }}public string OnClientDragStarting{get { return (string)ViewState[“OnClientDragStarting”]; }set { ViewState[“OnClientDragStarting”] = value; }}public string OnClientDropped{get { return (string)ViewState[“OnClientDropped”]; }set { ViewState[“OnClientDropped”] = value; }}protected override Ienumerable GetScriptDescriptors(IScriptRegistrar registrar){ScriptControlDescriptor descriptor = new ScriptDescriptor(this.GetType().FullName, this.ClientID);descriptor.AddProperty("operation", this.Operation);if (!string.IsNullOrEmpty(this.OnClientDragStarting))descriptor.AddEvent(“dragStarting”, this.OnClientDragStarting);if (!string.IsNullOrEmpty(this.OnClientDragEnding))descriptor.AddEvent(“dragEnding”, this.OnClientDragEnding));if (!string.IsNullOrEmpty(this.OnClientDropped))descriptor.AddEvent(“dropped”, this.OnClientDropped);yield return descriptor;}protected override void GetAjaxScriptReferences(IScriptRegistrar registrar){List references = new List();References.Add(new ScriptReference{Assembly = typeof(DragDropPanel).Assembly.Name,Name = “Nucleo.Web.DragDropPanel.js”});registrar.AddReference(new ScriptReferencingRequestDetails(Page.ResolveClientUrl(_manager.JQueryScripts.CoreScriptPath), ScriptMode.Release));registrar.AddReference(new ScriptReferencingRequestDetails(Page.ResolveClientUrl(_manager.JQueryScripts.CoreUIScriptPath), ScriptMode.Release));registrar.AddReference(new ScriptReferencingRequestDetails(Page.ResolveClientUrl(_manager.JQueryScripts.DragScriptPath), ScriptMode.Release));registrar.AddReference(new ScriptReferencingRequestDetails(Page.ResolveClientUrl(_manager.JQueryScripts.DropScriptPath), ScriptMode.Release));}protected override void Render(HtmlTextWriter writer){ScriptManager manager = ScriptManager.GetCurrent(this.Page);manager.RegisterScriptDescriptors(this);base.AddAttributesToRender(writer);writer.RenderBeginTag(HtmlTextWriterTag.Div);Panel panel = new Panel();if (this.ContentTemplate != null)this.ContentTemplate.InstantiateIn(panel);panel.RenderControl(writer);writer.RenderEndTag();}}

Starting with events on the server, while events in C# or VB.NET take a different form, an event in ASP.NET AJAX takes the form of a property, reading, or writing a string. This is because this property identifies the name of a JavaScript method, which is passed to the client-side component. Events in JavaScript don’t work the same way and cannot directly translate to an event in C# or VB.NET, and so events have to work in this fashion.

Additionally, each control—whether AJAX-enabled or not—goes through a rendering process. The Render method is where the UI of the control takes form. This is where the fourth property, ContentTemplate, comes into play. Some controls can take full control over the UI, while other controls don’t have any control over it. Controls that use templates fit within the latter category, and as such give the consumer the control over how the UI will take shape.

At rendering time, the template provided is rendered through a container control (specified via the InstantiateIn method). Every control has the RenderControl method, which renders that control’s content (calls its Render method) using the specified HtmlTextWriter object. At the end of the rendering process, every control’s content rendered is flushed to the browser, in its final stateless form. Using the control is easy. Figure 5 provides an example in a test ASPX page, included in the sample code for this article.

First PanelSecond Drag PanelThird Drag Panel
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like