ACE in the Hole - 30 Oct 2009

A JavaScript Component for AJAX

LANGUAGES: ALL

ASP.NET VERSIONS: 2.0

Asynchronous JavaScript and XML (AJAX) is a recent development in Web applications that s worth your attention. However, AJAX is not a technology - let alone a new technology. It is several not so new technologies combined to offer an arguably new Web application model.

The Web was originally created to provide an easy way to exchange static documents. A user requests a document by typing an address in a Web browser. The browser sends the request to a Web server. The server finds the document and returns it to the browser. The browser renders the document for the user. There is no question that the Web plays its intended role very well.

But the Web has rapidly evolved into a medium for the exchange of dynamic data. Web sites are being replaced by Web applications. Web servers compose documents depending on requests instead of simply retrieving them. However, the process of requesting data is still the same. The user must wait for the whole document to be re-rendered when they need additional data. This is a far cry from the instant response of Windows applications to which the user has become accustomed. Web applications are asking the Web to do a job for which it is not designed.

One of the core technologies used by AJAX is XML HTTP request, which enables the browser to request data from the server asynchronously. The browser updates the affected part of the document as soon as it receives the data. While all this is happening behind the scene, the user keeps working with the document. Suddenly, Web applications can be almost as responsive as their Windows counterparts.

AJAX technologies are not particularly difficult to use, but they have their own share of nuances and gotchas that you would prefer to not deal with. In addition, with more and more AJAX-style Web applications being built, there emerges the need for a framework to address common issues. Microsoft has begun building an AJAX framework for ASP.NET, called Atlas, which is currently in beta. However, instead of waiting for theirs, I built my own component in JavaScript called the AJAX Client Engine (ACE). In this article, I ll show you why and how ACE can make you a productive AJAX developer in no time.

 

Features

ACE is designed to make it easy to develop Web applications based on the AJAX model. The component has the following features that collectively realize the design:

  • Object-oriented API. The component encapsulates the details of creating and calling the XMLHTTPRequest object completely. It provides an object-oriented API that is simple, yet powerful. The user only need become familiar with three JavaScript classes (Engine, Request, and Response) to access all the functionality of the XMLHTTPRequest object, as well as additional framework services.
  • Cross-browser support. The component handles the differences between Internet Explorer and Mozilla Firefox browsers in creating the XMLHTTPRequest object, transforming XML documents, and so on.
  • Request options. The user can make either an asynchronous or a synchronous request by either providing a callback function or not.
  • Request parameter validation. The component throws exceptions with a meaningful description when it encounters invalid request parameters.
  • Callback arguments. The user can have the component pass any object to the callback function. This feature can be helpful when the user wants to use a single callback function to handle multiple requests.
  • Callback options. The user can choose the condition under which the callback function will be called, when the status of the XMLHTTPRequest object is OK, when the ready state of the object is Completed, or when the ready state of the object is ever changed.
  • Tracing service. The component has a built-in tracing service that will display detailed information about the execution of a request in a separate browser window. To use the tracing service, the user only need provide an optional engineId parameter when creating an engine object.
  • Caching service. The component has a built-in caching service that will store a response for a specified period of time. To use the caching service, the user only need provide an optional expiration parameter when calling the invoke method of an engine object.
  • Polling service. The component has a built-in polling service that will repeat a request at a specified interval. To use the polling service, the user only need provide an optional interval parameter when calling the invoke method of the engine object.
  • Common callbacks. The component defines several callback functions that perform more common tasks with the response received. The user can use them directly instead of creating custom callback functions.
  • Exception handling. The component defines all the exceptions it will potentially throw. The user can use the familiar try..catch structure for exception handling.

 

First Example

The best way to learn about a component is to see the code where it s used. I built a couple of examples for this article (these are available for download; see end of article for details). They are made of two HTML pages and four ASP.NET pages. One of them, examples.html, directly uses the ACE component; it includes the following line in the head section:  

 

You ll need to have IIS and the .NET Framework installed on your computer to run the examples. First, download the source code and place it in a local folder. Second, set the folder as an IIS virtual directory. Third, open Internet Explorer (version 5 or later) or Mozilla Firefox and enter http://localhost/[virtual directory name]/default.html as the URL. Figure 1 shows the page that will be displayed in the browser.

 


Figure 1: The example page.

 

The first example is a progress bar created using ACE. It is intended to be used on a page where the user can start a long-running process on the server. The progress bar continuously informs the user how far the process has gone while the user is performing other tasks on the same page.

To see the progress bar in action, click the Start Process button. A blue zone will start to fill the bar from left to right. A caption in the bar will periodically update to indicate the current stage of the process. Click the button a couple of times to pause and resume the process.

You may have noticed the top of the page where the progress bar is located has not been re-rendered even once. You may also have noticed the bottom of the page is continuously updating when the progress bar is running. And, the bottom page stops updating as soon as the progress bar is paused. What is happening here?

Actually, as soon as you click the Start Process button, the top of the page starts to periodically request from the server the status of the long-running process. It stops and re-starts requesting when you pause and resume the progress bar. The bottom of the page is a tracing tool provided by ACE that displays detailed information about the execution of all running ACE engines. Figure 2 shows the HTML for the example.

 


Figure 2: The HTML for the progress bar example.

 

You can see that the progress bar is made from three div elements, of which two are placed inside the third one. A function, btnStartProcess_click, handles the onclick event of the button.

Let s look at the first part of the client script for the example:

var progressBarEngine =

 newAce.Engine("progressBar", "_tracer"); 

The script creates an instance of the Ace.Engine class. Two optional parameters, engineId and tracerId, are provided to the constructor. As a result, the engine will load the tracer in a browser window whose name is _tracer and add progressBar in the beginning of every entry of trace information from the engine. Because _tracer is the name of the bottom frame of default.html, the tracer is loaded in the frame instead of a separate browser window. Figure 3 shows the second part of the client script.

function btnStartProcess_click()

{

if (document.getElementById("btnStartProcess").value

 == "StartProcess" || document.getElementById(

 "btnStartProcess").value== "Resume Process") 

 { 

var request = new Ace.Request(Ace.Method.Get,

 "LongProcess.aspx", null, null,progressCallback,

 null,Ace.CallbackOption.StatusOK); 

 progressBarEngine.invoke(request, null, 0.5); 

 document.getElementById("btnStartProcess").value=

 "PauseProcess"; 

 } 

 

 else

 { 

 progressBarEngine.cancel();

 document.getElementById("btnStartProcess").value =

 "ResumeProcess"; 

 } 

}

 Figure 3: The function that handles the click event of the Start Process button.

 

btnStartProcess_click is the function that handles the click event of the button. If the process is running, the function calls the cancel method of the engine. The effect of the method should be self-explanatory. If the process is not running, the function first creates an instance of the Ace.Request class and then calls the invoke method of the engine. The invoke method internally creates an XMLHTTPRequest object that will do all the heavy lifting. Not to confuse it with an instance of the Ace.Request class, I will refer to it as XML HTTP request in further discussions.

Because creating a request object is the most complicated step in using ACE, let s examine each parameter one by one:

  • The method parameter is set to Ace.Method.Get, so the method of the XML HTTP request will be GET.
  • The url parameter is set to LongProcess.aspx, so the XML HTTP request will be sent to LongProcess.aspx.
  • The headers parameter is set to null, so no headers will be added to the XML HTTP request.
  • The content parameter is set to null, so the XML HTTP request will not send any data to the server.
  • The callback parameter is set to progressBarCallback, a function we ll examine later, so the XML HTTP request will be asynchronous and the engine will call progressBarCallback when the condition set in the last parameter is met.
  • The callbackArgs parameter is set to null, so the engine will not pass any object when it calls progressBarCallback.
  • The callbackOption parameter is set to Ace.CallbackOption.StatusOK, so the engine will call progressBarCallback only if the status of the XML HTTP request is 200.

The btnStartProcess_click function passes three parameters to call the invoke method. The first parameter, request, is set to the request object just created. I will not discuss the second parameter until the next example. The third parameter, interval, is set to 0.5. As a result, the engine will repeat the same request every half a second until its cancel method is called or the browser unloads the page. Figure 4 shows the third and final part of the client script.

function progressCallback(response, args) 

{

 var result =response.text.split(","); 

 document.getElementById("divProgress1").style.width =

 result[0]; 

 document.getElementById("divProgress2").innerHTML =

 result[1]; 

 if (result[0] =="100%") 

 { 

 progressBarEngine.cancel();

 document.getElementById("btnStartProcess").value =

 "StartProcess"; 

 } 

}

 Figure 4: The callback function for the progress bar example.

progressBarCallback is the callback function that the engine will call when the status of the XML HTTP request becomes 200. The engine passes two parameters to a callback function. The response parameter is an instance of the Ace.Response class, which contains the result of the XML HTTP request. The args parameter is the object initially passed to the engine through the callbackArgs property of the request object. The progressBarCallback function parses the value of the text property of the response object, and changes the attributes of the div elements from which the progress bar is made.

That s all for the first example. You have seen that ACE completely hides the nitty-gritty details of dealing with the XMLHTTPRequest object. At the same time, ACE provides full access to the functionality of the object. You have also seen that ACE provides two powerful and yet easy-to-use framework services. The tracing service makes it easier to debug client script involving asynchronous XML HTTP requests. The polling service addresses one of the most common issues with using AJAX.

 

Second Example

The second example is an auto-complete behavior created with ACE. It is intended to be used on a page where a full lookup list is too large to download with the page. Traditionally, the user is required to click a button to open a search page in a separate browser window. On the search page, the user first enters the first few letters or digits of the item he wants to look up and then clicks a button to post the search criteria. When the page is re-rendered with the search result, the user scrolls up and down to find the item he is looking for. Finally, he selects the item found and closes the separate browser window. Now let s see how the auto-complete behavior solves the problem differently.

Start by typing one or more digits into the Order Number textbox. Almost instantly there appears a list below the textbox. All the order numbers that match the typed digits are displayed in the list. The list becomes shorter when more digits are being typed. If you double-click one of the order numbers, the selected order number will be automatically entered into the textbox and the list will disappear. Pretty slick, isn t it? Figure 5 shows the function that handles the keypress event of the Order Number textbox.

function txtOrderNumber_keypress(e) 

{

 var lastDigit; 

 // omit code thatsuppresses non-digit key strokes

 

var content = "leading=" + document.getElementById(

 "txtOrderNumber").value +lastDigit.toString();

 var callbackArgs = {id:"divOrderNumber" , attr:

 "innerHTML",xslString: xslStr};

var request = new Ace.Request(Ace.Method.Post,

 "OrderNumber.aspx", null, content,Ace.XmlCallback,

 callbackArgs,Ace.CallbackOption.StatusOK); 

 autoCompleteEngine.invoke(request, 60); 

 if(document.getElementById("cboOrderNumber")) 

 { 

 document.getElementById(

 "cboOrderNumber").style.display = "block"; 

 } 

}

 Figure 5: The function that handles the keypress event of the Order Number textbox.

The function first determines the last digit typed in the textbox. Then it creates a request object:

  • The method is POST.
  • The URL is OrderNumber.aspx.
  • No headers are added.
  • The content contains all the digits typed.
  • The callback parameter is set to Ace.XmlCallback.
  • The callbackArgs parameter is set to a custom object that has three properties (id, attr, and xslString).
  • The callback function will be called when the status of the XML HTTP request is 200.

The request object differs from the one in the first example in two aspects. First, it specifies that dynamic data are to be posted to the server. Second, and more importantly, it specifies that the callback function is not one defined by the user, but rather a function defined by ACE, Ace.XmlCallback. The function will first retrieve an XSL document from the xslString property of the callbackArgs object, retrieve an XML document from the text property of the response object, and transform the XML document with the XSL document. Figure 6 shows the XSL document used to set the xslString property of the callbackArgs object.

 

 

  

  

  

  

  

  

 

 Figure 6: The value of the xslString property of the callbackArgs object.

  Here s an excerpt of the XML document contained in the text property of the response object:

 

 100352 

 101293 

 .... 

 986453 

 

You can see that the result of the transformation is some HTML text representing a select element populated with order numbers. The Ace.XmlCallback function will find an HTML element whose id is divOrderNumber as specified by the id property of the callbackArgs object. Finally, the function will assign the result of the transformation to the innerHTML attribute of the element, which is specified by the attr property of the callbackArgs object.

Once the request object is created, the script for the example calls the invoke method of the engine:

autoCompleteEngine.invoke(request, 60); 

The third parameter of the call, interval, is omitted, so the request will not be repeated. However, the second parameter, expiration, is set to 60. As a result, the engine will cache the response for 60 seconds. The key of the cached response is constructed from the url and content properties of the request object. Cached responses can be accessed by all the engines on the same page. To see the caching service in action, first type six digits one by one into the Order Number textbox and then delete them all. Now, re-type the same six digits and watch the tracer in the bottom frame. You can see that the engine finds the requested data already in the cache and fetches them instead of making new XML HTTP requests.

From the second example, you have learned two more framework services provided by ACE. The caching service boosts the performance of Web applications by minimizing the number of requests to the server. The common callback service boosts the productivity of AJAX developers by minimizing the number of callback functions to be coded.

These two examples only illustrate the most important features of ACE. The next section covers the programming interface of the component in detail. However, before continuing with this serious matter, let s have some fun first. Start all the examples at the same time, as illustrated in Figure 7, and calmly watch the tracer go crazy.


Figure 7: All the bells and whistles.

 

API Reference

ACE is implemented in object-oriented JavaScript, which is not a strongly typed language. Its implementation of object-oriented programming is not class-based, but rather prototype-based. Nevertheless, for all practical purposes, the user can consider ACE to be a normal class library.

 

The ACE component contains only three JavaScript classes that serve as normal classes. All other classes serve as enumerations or constants (see Figure 8).

 

Class

Description

Ace

An empty class that serves as a namespace.

Ace.Engine

Provides core functionalities of ACE (see Figure 9 for details).

Ace.Request

Contains parameters needed to call the XMLHTTPRequest object (see Figure 9 for details).

Ace.Reponse

Contains information returned from the XMLHTTPRequest object (see Figure 9 for details).

Ace.Method

Enumerates HTTP methods accepted by the open method of the XMLHTTPRequest object:

  • Get: GET
  • Post: POST
  • Head: HEAD

Ace.ReadyState

Enumerates possible values of the readyState property of the XMLHTTPRequest object:

  • Uninitialized: 0
  • Loading: 1
  • Loaded: 2
  • Interactive: 3
  • Complete: 4

Ace.Status

Enumerates common values of the status property of the XMLHTTPRequest object:

  • OK: 200
  • Created: 201
  • Accepted: 202
  • NoContent: 204
  • BadRequest: 400
  • Unauthorized: 401
  • Forbidden: 403
  • NotFound: 404
  • MethodNotAllowed: 405
  • RequestTimeout: 408
  • Gone: 410
  • ServerError: 500

Ace.CallbackOption

Enumerates conditions under which the callback function will be called:

  • StatusOK: 1 (when the status of XMLHTTPRequest is 200)
  • ReadyStateComplete: 2 (when the readyState of XMLHTTPRequest is 4)
  • Always: 3 (when the readyState of XMLHTTPRequest is changed)

Ace.Exception

Enumerates exceptions that the ACE component can throw:

  • XmlHttpNotSupported: XMLHTTPRequest is not supported by the browser.
  • XmlDomNotSupported: XML DOM is not supported by the browser.
  • XmlIslandNotSupported: XML Data Island is not supported by the browser.
  • InvalidMethod: method must be GET , POST , or HEAD .
  • MissingUrl: url must be specified.
  • InvalidHeaders: headers must be an object.
  • InvalidContent: content must be a string of the format name1=value1&name2=value2 .
  • IllegalContent: content must be null when method is GET or HEAD .
  • IllegalInterval: interval can be set only if callback of request is set.
  • InvalidCallbackArgs: args for the callback function misses certain properties.

Ace.Constant

Defines constants that are used by the ACE component:

  • DefaultTracerId: Ace_Default_Tracer
  • NotFoundInCache: Ace_Not_Found_In_Cache

Figure 8: Classes of the ACE component.

 

The Ace.Engine class is the most important class of the component. It is also the only one with method members (see Figure 9).

 

Member

Description

Ace.Engine

Constructor. The first optional parameter, engineId, specifies a string that will be added at the beginning of every entry of trace information for the current engine. The second optional parameter, tracerId, specifies the name of a browser window in which trace information will be displayed.

 

If the engineId parameter is omitted, the tracing service will not be started. Otherwise, the tracing service will be started as follows: If there is a browser window currently opened for tracing for other engines, it will be used for tracing for the current engine as well. Otherwise, a new browser window will be opened. If the tracerId parameter is provided, it will be used as the name of the new window. Otherwise, the name of the new window will be Ace.Constant.DefaultTracerId.

ready

Property. Returns true when the engine is idle; returns false when the engine is executing an XML HTTP request.

invoke

Method. The first and mandatory parameter, request, is an instance of ACE.Request class. The second and optional parameter, expiration, is a number specifying the duration in seconds for which the response will be stored in cache. The third and optional parameter, interval, is a number specifying the interval in seconds by which the same request will be repeated. The return value is null for an asynchronous request or an instance of the ACE.Response class for a synchronous request.

 

If the browser does not support XML HTTP request, an XmlHttpNotSupported exception will be thrown.

cancel

Method. Ends the current executing request if there is one, and stops the polling service if it s running.

Figure 9: The Ace.Engine class of the component.

The Ace.Request class has property members only, all of which can be set through its constructor (see Figure 10). It is normally instantiated by the user to pass the parameters for making an XML HTTP request to an instance of the ACE.Engine class.

Property

Description

method

An optional string that specifies the method in which the request will be sent. The default is ACE.Method.Get. It must be a member of the ACE.Method class; otherwise, an InvalidMethod exception will be thrown.

url

A mandatory string that specifies the URL to which the request will be sent. If it s omitted, a MissingUrl exception will be thrown.

headers

An optional object whose property names and values correspond to header labels and values to be set for the request.

content

An optional string that will be sent with the request by ACE when the method is ACE.Method.Post. It must be in the form of name1=value1&name2=value2 ; otherwise, an InvalidContent exception will be thrown. If it s provided, but the method parameter is not ACE.Method.Post, an IllegalContent exception will be thrown.

callback

An optional function that will be called by ACE for the asynchronous request. The function must take two parameters, response and args, and return no value. If it s provided, the request will be asynchronous; otherwise, the request will be synchronous.

callbackArgs

An optional object that will be passed to the callback by ACE. If it s provided, but the callback parameter is omitted, an IllegalCallbackArgs exception will be thrown.

callbackOption

An optional number specifying the condition under ACE will call the callback. The default is ACE.CallbackOption.StatusOK. It must be a member of the ACE.CallbackOption class; otherwise, an InvalidCallbackOption exception will be thrown. If it s provided, but the callback parameter is omitted, an IllegalCallbackOption exception will be thrown.

Figure 10: The Ace.Request class of the component.

The Ace.Response class has property members only (see Figure 11). It is normally instantiated by an instance of the ACE.Engine class to pass the result of an XML HTTP request to the user.

Property

Description

readyState

A number set with the value of the readyState property of the private XMLHTTPRequest object. It can be expressed as a member of the ACE.ReadyState class.

status

A number set with the value of the status property of the private XMLHTTPRequest object. It can be expressed as a member of the ACE.Status class.

headers

An object whose property names and values correspond to header labels and values returned by the headers property of the private XMLHTTPRequest object.

Text

A string set with the value of the responseText property of the private XMLHTTPRequest object.

Xml

An XmlDocument object set with the value of the responseXML property of the private XMLHTTPRequest object.

Figure 11: The ACE.Response class of the component.

The ACE component has defined several functions that can be used as callbacks to perform the most common tasks on the response received (see Figure 12). Although they have the same signature as a custom callback function, a response parameter and an args parameter, the pre-defined callback functions require that the args object must have certain properties:

1) id, the id of a DHTML object;

2) attr, an attribute of the DHTML object; and

3) style, a style attribute of the DHTML object.

The id property and either the attr or style property must be set.

Function

Description

Ace.HtmlCallback

The function sets the attribute or the style attribute of the DHTML object to the response text.

Ace.ScriptCallback

The function executes the response text because it is JavaScript code first, and it sets the attribute or the style attribute of the DHTML object to the result of the execution.

Ace.XmlCallBack

The function requires the args object to have one of two additional properties: either:

1) xslString, a string containing an XSLT document; or

2) xslId, the id of an XML data island that contains an XSL document.

 

The function transforms the response text with the XSL document first, and it sets the attribute or the style attribute of the DHTML object to the result of the transformation. If the xslId property is set and the browser does not support XML data island, an XmlIslandNotSupported exception will be thrown. If the browser does not support XML DOM, an XmlDomNotSupported exception will be thrown.

Figure 12: The common callback functions defined by the component.

 

Conclusion

AJAX is poised to fundamentally reshape how Web applications will be developed. Coming with the opportunity are several challenges. Writing robust and maintainable client script is one of them. ACE encapsulates the functionality of the XMLHTTPRequest object inside a simple, yet powerful programming interface. It is simple because it consists of only three classes, only one of them has methods, and there are only two methods. It is powerful because it not only provides full access to all the capabilities of the XMLHTTPRequest object, but also provides essential framework services, including tracing, caching, polling, and common callbacks. Using ACE, you only need to write a couple of lines of code to create such advanced features as a progress bar or auto complete.

ACE can be used alone on the client side regardless of which technology is used on the server side. It can also be incorporated into a framework that spans from the server to the client side. A framework of this kind takes on another challenge by AJAX; that is, how to integrate AJAX with current platforms for Web application development. The framework should obviously be able to generate client script automatically. More importantly, however, it should be able to provide services that address issues unique to the AJAX Web application model. Using ACE, you only need concern yourself with the server side to create such an overarching framework.

The examples referenced in this article are available for download.

Li Shen is Senior Technical Consultant with Perficient, a leading information technology consulting firm in the central United States. He has more than nine years of experience designing and implementing Windows and Web applications for a variety of industries. He is an MCSD and a Sun Certified Java Programmer. He can be reached at mailto:[email protected].

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