devxlogo

Generalized AJAX Response Handling for XML Content

Generalized AJAX Response Handling for XML Content

JAX is catching on, but handling multiple XML responses on the client side is difficult. Current methods of easing the hassle ignore important programming principles that are known to help avoid maintenance headaches down the road. This article presents an elegant method of handling any number of XML responses in an AJAX application, while adhering to the principles of abstraction and encapsulation.

The ability to asynchronously transfer XML documents between the client and server without refreshing the entire page opens up an entire field of possibilities for browser-based Web applications. Indeed, it changes the way you can and must think about Web applications, because new problems arise from asynchronous communications?especially when using the XMLHttpRequest and XMLHTTP objects. In this article, you’ll explore some of these issues and develop an elegant method for handling them, all while adhering to important programming principles.

Abstraction and Asynchrony are Antagonists in AJAX
When AJAX makes a request, JavaScript calls a specified event-handling function when the readyState property of the XMLHttpRequest object changes. This event handler should check the properties of the request to see whether readyState has changed to 4 (completed) and whether the HTTP response status code is 200 (OK). Apple’s developer site suggests the code below, which has become relatively standard. Note that this response-handling code depends on the properties and expected values of the XMLHttpRequest and XMLHTTP objects, because this will become important later.

   function processReqChanged() {      // only if req shows "loaded"      if (req.readyState == 4) {         // only if "OK"         if (req.status == 200) {            // ...processing statements go here...         } else {            alert("There was an error.");         }      }   }

The preceding code does a fine job of handling a small number of response types.

What You Need
To gain the most benefit from this article, you should be comfortable with client-side JavaScript and have a general familiarity with XML and server-side scripting. Additionally, you should be familiar with the concept and implementation of AJAX techniques (see the links in the Related Resources section if you need a refresher). To run the downloadable sample code included with this article, you need an AJAX-capable Web browser, such as the latest version of Firefox, Mozilla, Opera, Konqueror, Safari, or Internet Explorer. You also need a Web server with PHP version 4 installed.

You have to check the response values because typically, Web requests via the XMLHttpRequest object to a server are asynchronous (although you can make synchronous requests, there’s little reason to do so in Web applications). Bearing that in mind, consider the pseudo-code in Listing 2, which sends two requests. After sending the first request, the method immediately returns and execution continues on to send a second request in the subsequent lines.

   req.open(url1);   req.send(requestXML);   req = getNewReqInstance();   req.open(url2);   req.send(requestXML);

To demonstrate, assume the preceding code is for an application that checks for the availability of a domain name. The first server script (url1) checks the databases of other registrars, while the second script (url2) checks your local database. The local check operation is fast, while checking other registrars is comparatively slow. To increase the apparent speed of your application, you have split these tasks into two separate requests. That way, if the domain name is unavailable in your local database, the browser can report the fact immediately. On the other hand, if the domain is available in your database then you can display a message indicating that you’re checking other registrars to confirm availability while the browser waits for a response from the remote database search.

When execution reaches the end of the preceding code fragment, the application has made two outstanding requests and is awaiting a response from each. But if you’re using the processReqChanged function shown earlier for both requests, how can you determine which request the current response is associated with? In other words, because response handling is asynchronous and event-driven, and both the local and remote requests fire the processReqChanged method, how can you know whether the method was fired by the local or the remote call?

You could guess that the first response received will be from your local database and the response from the other registrars will take longer, because it must access many remote databases. However, trying to develop time-based rules to handle asynchronous processing is a losing battle. Your rule could break down if the local database is temporarily down and the connection timeout is larger than the time it takes the other request to process. Other less extreme cases could also cause the return order to change. In other words, you need to use something other than time to interpret the response; you need to think asynchronously.

Thinking Asynchronously
Some developers approach this problem by assigning a different response-handling function to each request object created. That takes care of the differentiation problem, but now all the response processing code must have direct access to the request object and must include code that is specific to XMLHttpRequest and XMLHTTP object properties and methods. Each response-handling function would take the form of the processReqChanged function shown earlier, filtering ReadyStateChange events based on the readyState and status properties of the request object. If new objects or better techniques come along, you could end up needing to change a lot of code, instead of only the code that handles the communication. Instead, you should try to abstract the details of the communication from the response-handling code just as browsers and Web service frameworks abstract the details of HTTP connections from the process of sending the request.

That leaves the response content as the only remaining candidate for interpreting the nature of the response currently received. In this case you can be certain that if a response must be handled differently, then its content will be different. In this article, Drew McLellan suggests that this can be done by returning the name of the JavaScript function to call to handle the response within the XML response itself, and that a general processing function such as that shown at the beginning of this article can then use JavaScript’s eval() function to call the specified function. But that method raises security concerns. For example, an attack might alter the HTTP response in transit (imagine the users of an online shopping site receiving an alert box with “Haha, we just stole your credit card!”). More importantly, though, it violates the fundamental design principles of abstraction and encapsulation. The server-side script should not need to know the names of client-side JavaScript functions. If this method were to become general practice, it would lead to horrific maintenance problems?the very maintenance problems such design principles are meant to avoid.

A slight modification of McLellan’s suggestion offers some hope. If the returned “function name” is interpreted not as a function name but rather as a response type identifier, the client-side script could use a switch-case or if-then-elseif construct to dispatch the response XML to the correct handler. This is similar to assigning each request object its own handling function, except that all the AJAX-specific code would exist only in one place: the dispatching function. By dispatching only the XML response, it achieves the abstraction and encapsulation that we desire.

However, even this solution is too limited, as it restricts us to a one-to-one relationship between response and response handler. It could not elegantly handle a response of a complex type as shown below.

            chat                                                                                                                                                      2576                              

With a complex response, the handling function would have to branch off many times or pass off chunks to other functions. For example, Listing 3 includes a typeid element with the value “chat”. The distinct element value lets the client-side code dispatch the response to a special response handler for this particular response type. However, it would have to pass the entire response to this function, and the function would then need to branch further to handle the chatuserlist, chatmessages, and foo elements. More desirably the general handling function would pass off only the relevant parts of complex responses to appropriate handling code. In other words, you want your general response handling function to send the user list part of the response to the function that handles the user list; send the messages to the function that handles the chat messages, etc. Therefore, a general solution must be able to act appropriately on any element anywhere in the XML response. The best solution, then, is to register handling functions at the XML element level.

The Solution: An Aperiodic Table of Elements
First, you can now remove the response type identifier from the preceding response XML document, because you no longer need it. That leaves a cleaner response as shown below.

                                                                                 2576         

For demonstration purposes, consider a second type of response sent in response to an XML request to check whether a username is already taken. The response might look like this:

      setRespHandler. For example, you can create the mappings shown above using calls to setRespHandler as follows:
   setRespHandler('chatmessage', handleMsg);   setRespHandler('userlist', handleUserlist);   setRespHandler('username', handleUsername);

These calls are all you need to attach handlers to elements in XML responses. Note also that the mapping includes elements from multiple response types.

With your handlers appropriately assigned, your library code could then loop through every element in the XML response and pass the element (including its children) to the appropriate hander, if it exists:

   // this is within a loop over all elements   if (registeredHandlers[elementName])   {      // call the handler function.      registeredHandlers[elementName]();   }

The last line of the preceding code might look a little odd, but remember that the values of our mapping are literally function references.

Note that a response may contain many elements that do not have assigned handlers. In that case, the test for the element’s name in the registeredHandlers array returns false and no call to a handler will be made. This is exactly what you want. In this way, every element of interest is passed to its relevant handling function and there is no need to further branch the code within the handling functions. As a byproduct, this scheme also solves the problem of handling different response types. In fact, you no longer care about response types, for handling purposes. You care only about the elements within the response. Of course, the element names registered should be unique. However, because the code passes a node object to the handling function, the function has access to all that node’s properties, including parentNode. So, if it’s unavoidable, you can write code to handle the same element name differently, based on its parent node(s). You can find an example of accessing the parentNode from within the handling function in the downloadable sample source.

By abstracting and encapsulating the XML communication specifics you can create AJAX library code such as the sample code for this article. Additionally, if ever you decide to use a method other than XMLHttpResponse to send requests to and receive responses from a remote server, you simply change the library code implementation and can leave the rest of your code untouched. AJAX opens up a number of new opportunities, but it is important not to leave tried and true programming principles behind in the process.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist