HTML Post Loading Resources Framework (AJAX Based) – Part 2 – Loading and pasting simple content

In a previous article – Ajax-based Post Loading of resources in HTML pages – for reuse of resources and fast user feedback – I have introduced the concept of “post loading resources”. Post loading resources to me is the process of having the browser retrieve additional contents for an HTML Page after it has been loaded, much like the way the browser loads image-resources driven by the IMG tags in the document. In the previous article, I have listed a number of reasons why to use a post loading of resources approach and what kind of resources these can be and what we can do with them.

I will use this article to introduce the core of the “PLR framework” (not more than a few bits of JavaScript, most of them copied from someone clever) and illustrate it by having my very sparse HTML document flesh out itself inside the browser by post loading additional resources. Some of these resources are not on the same server as the original HTML document and have to be handled by a Server Side proxy, as is explained in my post Proxy Servlet for AJAX requests to multiple, remote servers, the code of which I will reuse in this article.

What I need are the following elements to get started

  • a way to make asynchronuous HtppRequests from the browser – multiple, simultaneous en potentially unrelated requests; these request perform the actual post loading of resources
  • a simple way of registering the need for a resource to be post-loaded, including its URL and the target for that content
  • a way to trigger the post load resources to be executed once the main HTML document is loaded

The ContentLoader

The first requirement is a step up from most AJAX examples found on the internet: they assume only a single AJAX request to take place and use global variables to store a handle to the XMLHttpRequest object. That will not do in our case, since we may have multiple requests running in parallel. So we need to wrap the request object and its context data in individual objects that we can work with in parallel. A very useful implementation of such an approach is described in the wonderful book Ajax in Action (Dave Crane and Eric Pascarello with Darren James October, 2005 | 680 pages, ISBN: 1932394613) (a shameless plug since I shamelessly reuse some code from this book). On a serious note: I really think it is a very good book as it approaches AJAX in very serious manner – describing the challenges in building serious, robust, secure and maintainable applications using AJAX concepts. It also provides a lot of insight in key web development aspects, such as JavaScript, CSS and XML handling as well as introducing a number of frameworks and tools. The source code for this book can be downloaded from the Manning website (ajax_in_action_source_code.zip). It won’t do you much good without the book itself, I suppose, but it contains the ContentLoader library that I will make use of in this article.

The ContentLoader object is a JavaScript object that holds references to the XmlHttpRequest object (instance) it uses to handle a specific request as well as a reference to the function it needs to call when the request is successfully completed – resulting in a response to work on. When a ContentLoader object is instantiated, a reference to this function is passed in, along with the URL the request is sent to and if necessary – not used in this article – the method (POST or GET), the content type and parameters to be included in the request. The ContentLoader object is defined in the ContentLoader.js library and looks as follows (and again: this code is part of the Ajax in Action book):

 /* namespacing object */
var net=new Object();

net.READY_STATE_UNINITIALIZED=0;
net.READY_STATE_LOADING=1;
net.READY_STATE_LOADED=2;
net.READY_STATE_INTERACTIVE=3;
net.READY_STATE_COMPLETE=4;


/*--- content loader object for cross-browser requests ---*/
net.ContentLoader=function(url,onload,onerror,method,params,contentType){
  this.req=null;
  this.onload=onload;
  this.onerror=(onerror) ? onerror : this.defaultError;
  this.loadXMLDoc(url,method,params,contentType);
  this.parameter = null;
}

net.ContentLoader.prototype.loadXMLDoc=function(url,method,params,contentType){
  if (!method){
    method="GET";
  }
  if (!contentType && method=="POST"){
    contentType='application/x-www-form-urlencoded';
  }
  if (window.XMLHttpRequest){
    this.req=new XMLHttpRequest();
  } else if (window.ActiveXObject){
    this.req=new ActiveXObject("Microsoft.XMLHTTP");
  }
  if (this.req){
    try{
      var loader=this;
      this.req.onreadystatechange=function(){
        net.ContentLoader.onReadyState.call(loader);
      }
      this.req.open(method,url,true);
      if (contentType){
        this.req.setRequestHeader('Content-Type', contentType);
      }
      this.req.send(params);
    }catch (err){
      this.onerror.call(this);
    }
  }
}

net.ContentLoader.onReadyState=function(){
  var req=this.req;
  var ready=req.readyState;
  if (ready==net.READY_STATE_COMPLETE){
    var httpStatus=req.status;
      if (httpStatus==200 || httpStatus==0){
        this.onload.call(this);
      }
      else {
        this.onerror.call(this);
      }
  }
}

net.ContentLoader.prototype.defaultError=function(){
  alert("error fetching data!"
    +"\n\nreadyState:"+this.req.readyState
    +"\nstatus: "+this.req.status
    +"\nheaders: "+this.req.getAllResponseHeaders());
}

I have made one small change to the ContentLoader object as described in the Ajax in Action book: I have added the parameter property, which can hold a reference to an object on whose behalf the Content is Loaded.

The Post Load Resource object

Every Resource that needs to be post-loaded is specified through a PostLoadResource object. This object contains a few simple properties. Note that in two future articles we will extend the set of properties a little with a refresh interval and a set of dependencies on other PLR objects to deal with for example XML & XSLT combinations. The PLR object’s properties are: the URL at which the resource to be loaded is located, the status (UNINITIALIZED, LOADING, LOADED_AND_PROCESSING or PROCESSED), a label (textual description, primarily for debugging purposes), a boolean that specifies whether for loading the resource a proxy is required (because the resource is located on another domain) , the elementId for the DOM element that is the target for the resource’s content (frequently a DIV) and a reference to the XmlHttpRequest object (initially set to null) that has handled the PLR operation. Below is the constructor function for this object:

amis.PostLoadResource=function
                   ( elementId
                   , url
                   , requireProxy // boolean indicating whether or not the resource must be acquired through a proxy from a remote domain
                   , label
                   ) {
  this.elementId=elementId;
  this.url=url;
  this.requireProxy = requireProxy;
  this.state = amis.STATE_UNINITIALIZED;
  this.label = label;
  this.req = null;
  this.id = null;
}

All PLR objects are stored in an array by the function addPostLoadResource():

var postLoadResources = new Array();

  // create a new PLR object and add it to the array of PLR objects to be dealt with when the page has loaded
  function addPostLoadResource
           ( elementId
           , url
           , requireProxy // boolean indicating whether or not the resource must be acquired through a proxy from a remote domain
           , label
           ) {
     var plr = new amis.PostLoadResource(elementId, url, requireProxy, label);
     var size = postLoadResources.push( plr ); // add a new PostLoadResource object to the array
     plr.id = size -1; // ensure the plr objects knows where it sits in the postLoadResources array
     return plr;
  }

New resources to post load are typically defined inside the body of the HTML document, using simple statements like:

        addPostLoadResource('PL2', 'dept.xml', false, 'POSTLOAD_XMLDEPT_LOADER');


In this example call, the target Element is a DIV with ID equals PL2. The name of the resource is dept.xml and since a proxy is not required to retrieve it – specified by the third parameter – it is located in the same directory that holds the HTML document itself. The label of this PLR is defined as POSTLOAD__XMLDEPT_LOADER.

Note that the PLR mechanism is loaded into the HTML document from a JavaScript library. The HTML document only contains the calls to addPostLoadResource().

Triggering the Post Load Resource objects into action

As we just saw, registering the Post Load Resource object is nothing more than adding the object to an Array. The actual processing of the PLR operations is mainly a question of iterating through the array and activating a dedicated ContentLoader object for each PLR object. Subsequently the Response from each ContentLoader object is processed and somehow pasted into the page. The starting point of this process is the onLoad event that the browser fires on the BODY element once the entire HTML document has been loaded, including the JavaScript libraries that are referenced from the HEAD section. The BODY element looks like:

    <body onload="goPostLoadResources();">

The HEAD section needs to include the two libraries with the ContentLoader code and the PostLoadResource stuff:

    <script src="ContentLoader.js" type="text/javascript"></script>
    <script src="PostLoadResourceDemo1.js" type="text/javascript"></script>
  </head>

The function goPostLoadResources() iterates through the array of PLR objects and processes them in turn.

  function goPostLoadResources() {
    // iterate array postLoadResources
    var i=0;
    var loader;
    for (i=0;i<postLoadResources.length;i++) {
      if (postLoadResources[i].state == amis.STATE_UNINITIALIZED) {
        postLoadSingleResource(postLoadResources[i]);
      }
    }// for
  }// goPostLoadResources

Every PLR object is dealt with in the function postLoadSingleResource(). This function creates a dedicated ContentLoader object that will do the AJAX magic – the actual HttpRequest. We tell this ContentLoader object to handle processing of the results when the request is completed and the response is successfully received to the function processPostLoad. And of course we link the ContentLoader object to the current PLR object, using the parameter property:

  function postLoadSingleResource(postLoadResource) {
      postLoadResource.state = amis.STATE_LOADING;
      if (postLoadResource.requireProxy) {
        loader = new net.ContentLoader('proxy?remotehost='+postLoadResource.url, processPostLoad);
      }
      else {
        loader = new net.ContentLoader(postLoadResource.url, processPostLoad);
      }
      loader.parameter = postLoadResource;
  }// goPostLoadResources

When the ContentLoader object is done processing the request and receiving the response, it calls upon the function processPostLoad() to do something useful with the results:

  // This function is called by the ContentLoader when the AjaxRequest is successfully completed
  // and the requested content is available for further processing.
  function processPostLoad() {
    var postLoadResource = this.parameter;
    postLoadResource.req = this.req;
    postLoadResource.state = amis.STATE_LOADED_AND_PROCESSING;
    startProcessing( postLoadResource);
  }// processPostLoad


  // This function is called when a postLoadResources is loaded
  // and all its dependents have been loaded as well
  // and the requested content is available for further processing.
  // There is a number of things we can do in terms of post-processing
  // * use the contents as is
  // * xslt transform the contents
  // * HTML scrape the contents
  // * split CSV or otherwise pre-process the results
  // taking the result of this first step, we can:
  // - paste into the innerHTML of a specified element, for example a DIV or TD
  // - copy into the value attribute of a Form element like a TEXTAREA or INPUT
  // - process into <OPTION> elements under a select
  // - copy into JavaScript memorystructure
  // - call custom processor to anything special like populate SVG object
  function startProcessing(postLoadResource) {
      if (postLoadResource.elementId) {
        // go find element and load contents
        var element = el(postLoadResource.elementId);
        if (element.value) {
          element.value = postLoadResource.req.responseText;
        }
        else {
          element.innerHTML = postLoadResource.req.responseText;
        }
      }
    postLoadResource.state = amis.STATE_PROCESSED;
  }// startProcessing

As you can see, there is a lot that startProcessing(), called by processPostLoad(), could do and little it currently does. We will expand on this in future articles. At the moment, the processing consists of locating the target DOM element for the PLR object, checking whether it has a value property – such as INPUT, TEXTAREA and other Form elements do. If it does, the responseText is pasted into the value property. Otherwise, it is pasted into the innerHTML property of the targetr Element; this is the easiest way to display contents in for example a DIV or a table cell (TD) .

An Example – Fleshing out an HTML document after it has been loaded

In this example, we will take a very simple, pretty bare HTML document. We will load it into the browser and then flesh it out with three post loaded resources: one Java source file from the local server is pasted into a DIV element, an XML file from the local web server is copied into a TEXTAREA and a file called something.txt (and I have Googled for a file with that name) is loaded from a remote server – using the ProxyServlet – and pasted into another DIV element. The resulting page looks like this in the browser:

HTML Post Loading Resources Framework (AJAX Based) - Part 2 - Loading and pasting simple content

The HTML required to bring this about is conscise:

 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1252"></meta>
    <style type="text/css">
      div{ font-family:serif;font-size:12px;height:200px;width:600px;overflow:auto; padding:5px; margin-top:2px; font-family:sans-serif}
    </style>
    <title>PostLoadResourceDemo1</title>
    <script src="ContentLoader.js" type="text/javascript"></script>
    <script src="PostLoadResourceDemo1.js" type="text/javascript"></script>
  </head>
    <body onload="goPostLoadResources();">
    <h3>Show the source code of the HttpProxyServlet</h3>
    <DIV style="background-color :yellow">
      <pre id="PL1">
        <Script language="JavaScript">
          addPostLoadResource('PL1', 'HttpProxyServlet.java', false);
        </Script>
      </pre>
    </DIV>
    <h3>Show the contents of the dept.xml document on the server</h3>
    <DIV >
      <Script language="JavaScript">
        addPostLoadResource('PL2', 'dept.xml', false, 'POSTLOAD_XMLDEPT_LOADER');
      </Script>
      <textarea id="PL2" cols="60" rows="10">No text yet.</textarea>
    </DIV>
    <br />
    <h3>Show the contents of a file on a remote server: http://dev.w3.org/cvsweb/XML/test/XInclude/ents/something.txt</h3>
    <DIV id="PL3" style="background-color :pink">
        <Script language="JavaScript">
          addPostLoadResource('PL3', 'dev.w3.org/cvsweb/XML/test/XInclude/ents/something.txt', true);
        </Script>
    </DIV>
</body>
</html>

The HTML mainly consists of three DIV elements and three calls to addPostLoadResource. It has referenced two JavaScript libraries – for ContentLoader and for PostLoadResource – and includes a little heading. What should be clear is that adding textual content to an HTML document is very simple: add a DIV and add a JavaScript function call with the correct URL. This allows us to reuse resources across pages, embed content from remote servers and present a page very fast to the end user, even while the secondary content is still loading. We have also laid the foundation for more advanced resources and more advanced processing of those resources. Very soon, in this theater.

Resources

Source code for this article (does not contain the Proxy Servlet): PostLoadResourceDemo1.zip

The installments in this series are

 

 

Ajax in Action (Dave Crane and Eric Pascarello with Darren James October, 2005 | 680 pages, ISBN: 1932394613

Ajax-based Post Loading of resources in HTML pages – for reuse of resources and fast user feedback (prequel to this article)

Proxy Servlet for AJAX requests to remote Servers

 

5 Comments

  1. dario June 3, 2007
  2. Sonya June 1, 2007
  3. Nikky May 31, 2007
  4. Taylor May 22, 2007
  5. trullie April 12, 2007