Your Ad Here
Google

Monday, March 05, 2007

 

ajax DropDownList

Introduction


AJAX (Asynchronous JavaScript and XML) has become so popular, thanks to Google Suggest. AJAX has opened the possibility to make more responsive and interactive web applications, bringing them closer to Windows form applications. Web developers are the bunch of guys who were happy at first. They have a new toy, which is composed of old toys they have neglected so far, and now they can make cool things with the toy. On the other hand, after getting their free account in GMail, end users demand more with their department web application. They hate postback, they want no refresh, and everything just stays there but still gets up-to-date. Is it possible?


Those demanding users are my motivation to create this custom control. Let me introduce AjaxDropDownList, my first attempt to contribute into AJAX world. AjaxDropDownList is a dropdownlist control that has the following features:



Although I call the control as AjaxDropDownList, it does not use XML to transfer the data. I use JSON (JavaScript Object Notation) by Douglas Crockford which is more lightweight and can be easily consumed by JavaScript as an object. I am aware of the potential and flexibility that XML offers compared to JSON. But in my case, JSON is enough to serve the requirements.


How to use the control


Starting from the sample project


Download and open the project in VS.NET 2003. Edit GetLookupData.aspx.cs and change the connection string to point to a valid NorthWind database. If for some reason you don’t have NorthWind database, you can download the database script from Microsoft. Just do a search on Google.


Once the solution is built successfully, run and browse the default.aspx. Evaluate if this is the control you are looking for.


What the demo is about


In this demo page, we have three AjaxDropDownLists: Customers (ddlCustomers), Orders (ddlOrders) and Products (ddlProducts).


Orders dropdownlist depends on Customers. It means if we make a selection in Customers dropdownlist, then the Order dropdownlist will be filtered based on our selection. Furthermore, Products is depending on Orders. So a selection on Customers will trigger a change in Orders, and subsequently trigger a change in Products. Please note that all this happens without any postbacks.


While playing around with the dropdownlist, you may encounter a JavaScript alert box showing an error message. It could mean a lot of things, but most probably your connection to the database is not working.


Now press the Submit button and the page will finally do a postback. The selected text of each dropdownlist will be shown on the right hand side. This is to demonstrate that the standard code to access the selection in ASP.NET DropDownList still applies to AjaxDropDownList.


What is in there


There are three important parts:



Code Walkthrough


AjaxDropDownList control uses JavaScript intensively. The JavaScript code is embedded into the control and will be injected into the response stream when the control is rendered. In order to minimize HTTP payload, the JavaScript code has been minimized using JavaScript Minifier. However, this will make debugging on the real code difficult and frustrating.


Therefore, in the sample project I provide the source code in its original format and all comments still in place. Please refer to SourceScript.aspx during this walkthrough.


XMLHTTP


JavaScript code utilizes xmlHttp object to make requests to the web server either asynchronously or synchronously. As the request can be made without refreshing the page, the web page looks more responsive and interactive. The method getXMLHTTP() is called to a get reference to the xmlHttp object, regardless of how the browser implements this object.


Controller


Every AjaxDropDownList is rendered as a <SELECT> element in HTML. Each of these elements has its controller, called AjaxDropDownController. The controller has a lot of things to do:



Think of controller in the context of Model-View-Controller, being the <SELECT> element as the view, the data that resides in the web server as the model, and the controller itself as the controller, although I don't want to emphasize this pattern as it is not fully implemented.


Performing asynchronous background request


When the controller needs to update its dropdownlist, it will call load() which in turn calls getSource(). While calling these methods, it may pass a filter string, which is the name-value pair of the dropdownlist that it depends to. Inside the getSource() method, a request URL is constructed which contains the id and filter parameters.

var requestUrl = baseUrl + "?id=" + self.lookupName;

if (filter != undefined && filter != "")
{
requestUrl += "&filter=" + filter;
}

Then after a reference to the xmlHttp object is secured, it will send the request. Note the last parameter in xmlHttp.open which is set to true to indicate an asynchronous request.

xmlHttp = getXMLHTTP(); 
if (xmlHttp)
{
xmlHttp.onreadystatechange = doReadyStateChange;
xmlHttp.open("GET", requestUrl, true);
xmlHttp.send();
}

As the nature of the request is asynchronous, we could not determine when the response will be available. Therefore, we assign an event handler doReadyStateChange to the onreadystatechange property. This event handler will be called each time the state of the request changes.

function doReadyStateChange(){
if (xmlHttp.readyState == 4)
{
if (xmlHttp.status == 200)
{
eval("var d=" + xmlHttp.responseText);
if (d != null)
{
populateList(d);
}
}
else
{
alert("There was a problem retrieving the XML data:\n" +
xmlHttp.statusText);
}
}
}
}

In doReadyStateChange, we check for readyState = 4 which means "complete" and status= 200 which indicates an "OK" HTTP status code. Once these conditions are satisfied, it is time to process the response stream.


Processing the response stream


As mentioned earlier, I used JavaScript Object Notation (JSON) to transfer data from the web server to the client. JSON is more lightweight than XML and can be easily converted into JavaScript object hierarchy.


For example, when we send a request like this:

http://localhost/GetLookupData.aspx?id=Product&filter=Order,10280

The server will return:

[{"value":"24","name":"Guaraná Fantástica"},
{"value":"55","name":"Pâté chinois"},
{"value":"75","name":"Rhönbräu Klosterbier"}]

We concatenate the responseText with another string to make a valid JavaScript statement and execute the statement using eval.

eval("var d=" + xmlHttp.responseText);

As a result, an in-memory object hierarchy is created as follows:

d +--- [0] +--- value: 24
| |--- name: Guaraná Fantástica
|
+--- [1] +--- value: 55
| |--- name: Pâté chinois
|
+--- [2] +--- value: 75
|--- name: Rhönbräu Klosterbier

We can traverse the object hierarchy with ease, for example:



This object is then passed to populateList(), which is responsible to populate the corresponding <SELECT> element. The populateList will first clear the option items from the select, then iterate through the object hierarchy and create new option items.


Observer pattern


AjaxDropDownController implements the observer pattern. An instance of AjaxDropDownController can be both the observer and the observable. Each instance keeps a list of observers, so that when the value changes in the corresponding dropdownlist, the controller can notify each observer about the changes.


The list of observers is kept in this array:

var observers = [];

Methods



Persisting content


When the content of the dropdownlist is changed in the client side, the change is not carried to the server side during postback. Therefore, we could not use the standard code to access the selected item in the dropdownlist, e.g. using SelectedItem or SelectedIndex of the dropdownlist. This raises an issue when we want to incorporate the control into a data binding framework or we simply could not afford to write special code to handle the control.


That is the reason why I keep the content of the dropdownlist in the client side. For every AjaxDropDownList, there will be one hidden field associated with it. This hidden field is the container of the dropdownlist content as a value delimited string, e.g.:

24|Guaraná Fantástica|55|Pâté chinois|75|Rhönbräu Klosterbier

The delimiter character is configurable in the control. Therefore, if you don't like '|', you can change to another character..


During postback, the value delimited string will be read and the corresponding items are created in the dropdownlist.


The server side


Discussion about the code is not complete without touching the server side because this is where the data comes from. Unlike the client side, the server side code is relatively simple. Basically, we need to handle the request sent by the xmlHttp and provide the data in the form of JSON.


It is up to you how you want to get the data from the database. The GetLookupData.aspx is not a good example as it is prone to SQL injection attacks. In real life situations, you may need to call the data access framework instead of directly connecting to the database. You may also need to cache the data in memory to save the roundtrip to database.


What's most important for the client side is the format of the response you return to the client side. It has to be in this format:

[ {"value":"value1","name","name1"},
{"value":"value2","name","name2"},
...
{"value":"valueN","name","nameN"} ]

where value1... valueN denote item values and name1... nameN denote item texts.


The custom control


AjaxDropDownList is a custom control that inherits from System.Web.UI.WebControls.DropDownList. It encapsulates all code to provide dynamic data population from the client side and let the dropdownlist participate in the linked dropdownlist chain.


It has four additional public properties on top of the standard properties from DropDownList:



Compatibility


The control has been tested using Microsoft Internet Explorer 6, Mozilla Firefox 1.04, and Netscape 8.02.


Wish List


Some works need to be done on the designer side. Comments and ideas are most welcome. If the community shows enough interest, I will invest more time to refine this control.


Revision History



Links




Labels:


Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?