Skip navigation

Building a Custom AJAX Control with the Help of jQuery

jQuery and ASP.NET AJAX play nicely together

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
function advagg_mod_1() {
  // Count how many times this function is called.
  advagg_mod_1.count = ++advagg_mod_1.count || 1;
  try {
    if (advagg_mod_1.count <= 40) {
       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 Panel
	


	
		Second Drag Panel
	


	
		Third Drag Panel
	






Drag panels 1 through 3 enable dragging through the operation property. This value, behind the scenes, is passed to the client-side component, along with the event handlers passed to OnClientDragEnding, and so on. The page sets up three event handlers to listen for these events, and to write a response to a label. As you will see, jQuery drag and drop is very efficient.

jQuery Considerations

The first consideration you have to envision is where to reference the jQuery scripts. jQuery provides reference to its scripts via jquery.com or via Google’s Content Delivery Network (CDN); however, you can download jQuery directly to your project and include it there. The last, and probably most favorable solution, is to use the content delivery network option (if the script is available). While the subject has been debated, I think the last option is the best, and here’s why.

Some people say that including jQuery locally in your project is the best solution. The file is served up from your web server location and is in the same location as your web site files. However, imagine the location of your web server within its country of origin. Now, imagine users across the country trying to access the web site directly. The reference to the jQuery file would cause a download to all of these locations. Now imagine a CDN that has several locations across the country. Downloading the jQuery file directory would be much faster.

To put it into perspective, in the US imagine that the web site resides at a host in California. Imagine that a CDN has locations in New York, Tennessee, Nevada, and Idaho. For users accessing your site from Hawaii, a user may experience some slowness using a CDN because the locations are a lot farther away. However, for the other 70 percent of the state, the CDN would perform much better in terms of serving up this file, because the CDN server is much closer to the origin of request.

Secondly, as you implement jQuery and your scripts, you will want to consider how you want to implement jQuery in your components. The GetScriptDescriptors method in the above DragDropPanel server-side component can refer to the jQuery scripts directly. However, a better long-term solution is more desirable, like the web configuration application settings, database, XML file, and so on are better solutions.

If you are developing and deploying one application that doesn’t share resources or code, then the application settings section is an OK option. But this means any component is tied to an external resource (external to the project with the components). The best solution is one tied to the same component, which in my personal opinion, is a custom configuration section or some sort of central component.

The first option, a custom configuration section, is a portable and configurable one. The code for the custom configuration section can reside in the same library as the DragDropPanel and other controls. Custom configuration sections inherit from ConfigurationSection and implement any data to store within the config file as primitive data types or collections. You can find more information about custom configuration sections from my DotNetSlackers article here.

Another option can be defining a centralized control that appears at the top of a page. This setup would be similar to the ScriptManager component, where only one component can be defined within the page. This control can simply expose the paths to the scripts via properties defined within the page’s markup. Additionally, default values can refer to the default scripts pointing to the jQuery web site or Google’s CDN site. Other factors may be:

  • Is a difference in version needed (1.2.6 vs. 1.3.2 all within the same library, due to the inability to upgrade)?
  • Do you desire flexibility to add additional scripts such as the sortable UI feature, or maybe a plug-in?
  • Do you want to add extensibility to developers who may write an add-on to your custom framework?

jQuery, which is very easy to embed within ASP.NET AJAX controls, is a very powerful scripting library that makes dragging and dropping HTML elements easy. ASP.NET AJAX is meant to develop JavaScript components in a managed way, and this article has shown how you can embed jQuery into these components.

Brian Mains ([email protected]) is a Microsoft MVP and consultant with Computer Aid, where he works with nonprofit and state government organizations.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish