Monday, June 12, 2006

A Simple AJAX Report Form

I have always been a big supporter of desktop applications. I have always found the trend to try and throw everything into a web application a questionable and ridiculous attempt to capitalize. This is especially true for transactional applications, such as banking software. In my opinion, the overhead more that outweighed the benefits in. Due to that line of thinking, I have been incredibly hesitant to try and accept AJAX. However, I must admit, after actually diving into it and trying a few simple pages, I can see where it can help to bridge the gap between my coveted desktop applications and the slow, kludgy web apps I have come to expect.

This article is in no way a tutorial on AJAX. There are plenty of excellent articles on AJAX programming, books, and all sorts of information to help someone get their feet wet. I recommend this IBM Developerworks Series. This article will simply highlight my experience with completing the form I discussed last article.

The goal of the form is to have it call a BIRT report and display the results in the form page itself, rather than making a page switch or display the resulting page in a frameset, which I have always found to be ugly. The parameters that will be passed into my BIRT report come from two form fields, which are populated using the calendar component mentioned in my last article.

Here are the JavaScript snippets used in the code.

<script language="javascript" type="text/javascript">

/* Create a new XMLHttpRequest object to talk to the Web server */
var xmlHttp = false;
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
try {
  xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
  try {
    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (e2) {
    xmlHttp = false;
@end @*/

if (!xmlHttp && typeof XMLHttpRequest != 'undefined') {
  xmlHttp = new XMLHttpRequest();

This is borrowed directly from the IBM developerworks page. This will create a new XMLHttpRequest object, regardless of which browser family is used (IE or Standard, I have only tested with Firefox, IE, and Mozilla).

function callServer() {
  // Get the start and end date from the web form
  var start_date = document.getElementById("start_date").value;
  var end_date = document.getElementById("end_date").value;
  var cd_loc = document.getElementById("cd_loc").value;

  // Only go on if there are values for both fields
  if ((start_date == null) || (start_date == "")) return;
  if ((end_date == null) || (end_date == "")) return;

  // Build the URL to connect to
  var url = "http://localhost:8080/birt_viewer/run?__report=RoomUtilizationReport.rptdesign&Start_Date=" + escape(start_date) + "&End_Date=" + escape(end_date) + "&LocationCode=" + escape(cd_loc);
  // Open a connection to the server"GET", url, true);

  // Setup a function for the server to run when it's done
  xmlHttp.onreadystatechange = updatePage;

  // Send the request

This function will make the call BIRT for my report using the xmlHTTPRequest object. First, we get the parameters by using the getElementByID functions for each of the following form fields, the start and end date which specify a date range to search in the report, and the cd_loc field, which specifies a location code in the report. This doesn’t matter since the report isn’t really the heart of this article. Next we make sure the required fields are not null. Then we build the BIRT Report Server URL to call to retrieve the report. Next, we setup the xmlHTTPRequest object using our constructed URL, and setup the callback function to the updatePage function, which I will discuss next. Then we send the request to the server. This will happen in the “background” of the page and the user will be none the wiser.

function updatePage() {
     //If we have recieved a response
     if (xmlHttp.readyState == 4)
          var imageElement = document.getElementsByTagName("processingIcon")[0];
          //If the parent node exists and is not null, process this
          if (imageElement)
               var parentElement = imageElement.parentNode;
          //Create the element to contain our returned HTML page from BIRT
          var childElement = document.createElement("report");

          //Set the innerHTML code to the returned report page
          childElement.innerHTML = xmlHttp.responseText;

          //Go through the DOM tree until we find the body element
          var bodyElement = document.getElementsByTagName("body")[0];
          //Find and remove the old section, if it exists
          if (bodyElement.hasChildNodes()) {
               for (i=0; i<bodyElement.childNodes.length; i++) {
                          var oldElement = bodyElement.childNodes[i];
                         if (oldElement.nodeName.toLowerCase() == "report") {
          //Append our Report result intot he HTML body
          //Display a animated gif to give the impresion of a processing bar
          var childElement = document.getElementsByTagName("processingIcon")[0];
          if (!childElement)
               var childElement = document.createElement("processingIcon");

               childElement.innerHTML = '<img src="images/animation_hourglass.gif">';

               var bodyElement = document.getElementsByTagName("body")[0];
               var reportElement = document.getElementsByTagName("report")[0];

               if (reportElement)
                    bodyElement.insertBefore(childElement, reportElement);

This is the most complex part of the page. The basic logic flows like this:
     If response. readyState from xmlHTTPRequest is == 4 then
          -Remove the processing icon if present
-Create new page element called report, and put result from request into this new section
-Remove all old report sections so there are no duplicate reports in the same page
-Add newly created section to the page
          -Check to see if processing Icon is present
-If no processing Icon is present, create a new section and display an animated Icon for the user to look at, giving the impression of processing the request. If there is an already present report section, insert before this. Otherwise, just put at the end of the page.
To check the readyState of the xmlHTTPRequest object, the following code is used:

          xmlHttp.readyState == 4

For reference, the readyState codes are as follows (as borrowed from here):
0 = uninitialized
1 = loading
2 = loaded
3 = interactive     
4 = complete
As far as the processing Icon, I could have put in a branch if the readyState was equal to 1, but I chose the lazier path. To create the Processing Icon, first I checked if the section containing it exists using the following code:
var childElement = document.getElementsByTagName("processingIcon")[0];
     if (!childElement)

This way, if childElement does not exist, then the element does not exist on the page. The reaction of the script to display the processing icon is based off of this. I am sure some JavaScript guru can tell me a better way to do this, however this works fine for me.

The rest should be self explanatory, or at least understandable if you read the IBM Developerworks articles.

A few things became pretty apparent while working with AJAX. First, AJAX not the ultimate answer to all the webs problems. While it is a cool architecture, AJAX alone lacks structure, and it lacks a formal framework. AJAX also has the potential to suffer from abuse, which is something I hate to break to the fanboys. Picture this, you’re the dirt-bag spammer/online advertiser. Pop-ups and cookies are no longer an option for you to trap personal information from browsers. So what do you do? AJAX is new, and no subject to suspicion just yet, so you create a simple function that posts the browsers information via an AJAX call, and since this is done Asynchronously, it is done without the users knowledge or consent. This is of course assuming that this is not already being done. AJAX also suffers from the inability to design itself. While it is a powerful tool, its usage can be less than spectacular if the design of the site is poor.

No comments: