The Gravy Framework

ajax.js

Summary

Collection of AJAX support routines.

Author: Bruce Wallace (PolyGlotInc.com)
Requires:

Version: 1.0


Class Summary
XMLreq Constructor for XMLreq "object" which encapsulates a REST-style request/reply transaction that returns XML or Text.

Method Summary
static Object GetXslDOM( xslURL )
           retrieve XSL DOM from given URL
static String XformDOM( xslDOM, xmlDOM )
           transform given XML DOM using given XSLT DOM
static String XformURL( xslURL, xmlDOM )
           transform given XML DOM using an XSLT retrieved from given URL

// This file uses JSDoc-friendly comments [ http://jsdoc.sourceforge.net/ ]

/**
 * @file         ajax.js
 * @fileoverview Collection of AJAX support routines.
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @requires     utils.js
 * @version      1.0
 */

/**
 * @class Constructor for XMLreq "object" which encapsulates a REST-style
 * request/reply transaction that returns XML or Text. This class makes
 * multiple simultaneous requests possible, however, a separate instance
 * of this class is required for each request. This constructor will
 * automatically cause this request to be immediately launched unless
 * launch is suppressed.<p>
 * If post data is defined then this request is made via a POST (otherwise
 * via a GET) HTTP request.<p>
 * Any instance of this class can be created in "test mode" that reads
 * XML directly from an XML file rather than making a POST/GET request for XML.
 * This class has helper methods that will transform the response XML via
 * an XSL document specified via filename or URL.
 * @param {String} url the URL to request XML from (or XML file path in test mode)
 * @param {Function} userCallbackFunction the callback to handle the XML when it arrives
 * [note: The callback will have this XMLreq object passed to it via
 *  both "this", and, as a single function argument.]
 * @param {String} optPostData optional post data string (should be "undefined" if not a POST req.)
 * @param {boolean} optWaitFlag optional "wait for reply" flag (i.e. DONT do this async if true)
 * @param {boolean} optSuppressLaunchFlag optional "suppress launch" flag
 * @param {boolean} optTestFlag optional "read test files" flag (i.e. read XML file rather than ask server)
 * @throws ErrMsg if missing required parameter(s)
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function XMLreq( url, userCallbackFunction, optPostData, optWaitFlag, optTestFlag, optSuppressLaunchFlag )
{
	// validate parameters
	if (url==undefined) throw "missing required parameter: URL";

	// initialize instance data
	this.name   = "XMLreq" + timestamp(); //mostly unique name
	this.url    = url;
	this.post   = optPostData;
	this.test   = optTestFlag;
	this.async  = optWaitFlag?false:true;
	this.usercb = userCallbackFunction || function(){ Error("Unhandled Reply=["+this.getResponseText()+"]"); };
	this.cb     = new Function( "GetGlobalVar(\""+ this.name +"\").cbwrapper()" );

	// save reference to "this" in page-level variable (to be retrieved in callback)
	SetGlobalVar( this.name, this );
	if (!optSuppressLaunchFlag) this.launch();
}

// ----------------------------------------------------------------------------
// define/override methods
// ----------------------------------------------------------------------------

/** method to return "this" as a human-readable string @type String */
XMLreq.prototype.toString = function(){ return ObjectToString(this); }

/** method to create new XMLHttpRequest object for "this" request
 * @return "XMLHttpRequest object" @type Object
 * @throws ErrMsg if XML Request Objects arent supported by Browser
 */
XMLreq.prototype.newRequestObj = function()
{
	function getObj( xmlReq )
	{
		// if in test mode, return an XML document
		if (xmlReq.test){
			var XMLDoc = new ActiveXObject("MSXML2.DOMDocument");
			XMLDoc.async = xmlReq.async;
			return XMLDoc;
		}

	    // has native XMLHttpRequest object?
	    if (window.XMLHttpRequest) return new XMLHttpRequest();
	 
	    // has IE/Windows ActiveX version?
	    if (window.ActiveXObject)
	    {
	         try      { return new ActiveXObject("Msxml2.XMLHTTP"   ); }
	         catch(e) { return new ActiveXObject("Microsoft.XMLHTTP"); }
	    }
	
		throw "XML Request Objects are not supported by this Browser.";
	}

	var o = getObj( this );
	o.onreadystatechange = this.cb;
	return o;
}

/** callback wrapper method to relieve user callback from gory details of 
 * XMLHttpRequest state transition handling.
 */ 
XMLreq.prototype.cbwrapper = function()
{
  // Once send() has been called, XMLHttpRequest will contact the server
  // and retrieve the data we requested; however, this process takes an
  // indeterminate amount of time. In order to find out when the object
  // has finished retrieving data, we must use an event listener. In the
  // case of an XMLHttpRequest object, we need to listen for changes in
  // its readyState variable. This variable specifies the status of the
  // object's connection, and can be any of the following:
  // 0 - Uninitialised 
  // 1 - Loading 
  // 2 - Loaded 
  // 3 - Interactive 
  // 4 - Completed
  //
  if (this.obj.readyState == 4)
  {
     // readyState increments from 0 to 4, and the onreadystatechange
     // event is triggered for each increment, but we really only want
     // to know when the connection has completed (4), so our handling
     // function needs to realise this. Upon the connection's completion,
     // we also have to check whether the XMLHttpRequest object success-
     // fully retrieved the data, or was given an error code, such as
     // 404: "Page not found". This can be determined from the object's
     // status property, which contains an integer code. "200" denotes
     // a successful completion, but this value can be any of the HTTP
     // codes that servers may return. If the request was not successful,
     // we must specify a course of action.
     //
	if (this.test || this.obj.status == 200)
	{
		this.usercb( this ); //support both method and subroutine calls
		ClearGlobalVar( this.name ); //we are done so clean up persistence store
	}
	// IE returns a status code of 0 on some occasions, so ignore this case
	else if (this.obj.status == 0)
    	 Break("DEBUG:zero xml status! msg["+ this.obj.statusText +"] for ["+this.name+"]");
	else Error(   "Bad XML retrieve status["+ this.obj.statusText +"] for ["+this.name+"]");
  }
}

/** method to launch "this" request using the following logic;<pre>
 * To send CGI variables using the GET request method, we have to
 * hardcode the variables into the open URL parameter:
 *    this.open("GET", "/query.cgi?name=Bob&email=bob&#64;example.com"); 
 *    this.send(null);
 * To send CGI variables using the POST request method, we have to
 * pass the variables to the send() method:
 *    this.open("POST", "/query.cgi");
 *    this.send("name=Bob&email=bob&#64;example.com");
 *</pre>
 */
XMLreq.prototype.launch = function()
{
	// Even though the XMLHttpRequest object allows us to call the open()
	// method multiple times, each object can really only be used for one
	// call, as the onreadystatechange event doesn't update again once
	// readyState changes to "4" (in Mozilla). Therefore, we have to create
	// a new XMLHttpRequest object every time we want to make a remote call.
	//
	this.obj = this.newRequestObj();
	if (this.test) { this.obj.load( this.url ); return; }

	// open() initialises the connection we wish to make, and takes two
	// arguments, with several optionals. The first argument is the type
	// of request we want to send; the second argument identifies the
	// location from which we wish to request data. For instance, if we
	// wanted to use a GET request to access feed.xml at the root of our
	// server, we'd initialise the XMLHttpRequest object like this:
	//              xmlReq.open("GET", "/feed.xml");
	// The URL can be either relative or absolute, but due to cross-domain
	// security concerns, the target must reside on the same domain as the
	// page that requests it.
	// The open() method also takes an optional third boolean argument that
	// specifies whether the request is made asynchronously (true, the default)
	// or synchronously (false). With a synchronous request, the browser will
	// freeze, disallowing any user interaction, until the object has completed.
	// An asynchronous request occurs in the background, allowing other scripts
	// to run and letting the user continue to access their browser. It's
	// recommended that you use asynchronous requests; otherwise, we run the
	// risk of a user's browser locking up while they wait for a request that
	// went awry. open()'s optional fourth and fifth arguments are a username
	// and password for authentication when accessing a password-protected URL.
	//
	this.obj.open( this.post?"POST":"GET", this.url, this.async );

	// if POST, we tell the server to expect form-type parameters
	if (this.post)
		this.obj.setRequestHeader("Content-Type","application/x-www-form-urlencoded;");

	// Once open() has been used to initialise a connection, the send() method
	// activates the connection and makes the request. send() takes one argument,
	// allowing us to send extra data, such as CGI variables, along with the
	// call. Internet Explorer treats it as optional, but Mozilla will return
	// an error if no value is passed, so it's safest to call it using:
	//                xmlReq.send(null);
	this.obj.send( this.post ? this.post : null );
}


/** get XML returned in the reply to "this" xml request
 * @return XML DOM @type Object
 */
XMLreq.prototype.getResponseXML = function()
{
	// return a DOM-structured object of any XML data that was
	// retrieved by the request. This object is navigable using
	// the standard JavaScript DOM access methods and properties,
	// such as getElementsByTagName(), childNodes[] and parentNode. 
	//
	return this.test ? this.obj : this.obj.responseXML;
}

/** get text returned in the reply to "this" xml request
 * @type String
 */
XMLreq.prototype.getResponseText = function()
{
	// return the data as one complete string. If the content
	// type of the data supplied by the server was text/plain
	// or text/html, then this is the only property that will
	// contain data. A copy of any text/xml data will be flattened
	// and placed here as an alternative to responseXML.  
	//
	return this.test ? null : this.obj.responseText;
}

/** return the output from transforming the response XML
 * with the XSL in the specified URL/filename
 * @type String
 */
XMLreq.prototype.Xform = function( xslURL ) {
	return XformURL( xslURL, this.getResponseXML() );
}

//////////////////////////////////////////////////////////////
// Process XML via browser-side XSL...
//////////////////////////////////////////////////////////////

/** retrieve XSL DOM from given URL
 * @return XSL DOM @type Object
 */
function GetXslDOM( xslURL )
{
    // load XSLT stylesheet document
    var xslDOM = new ActiveXObject("Msxml2.DOMDocument");
        xslDOM.async = false;
        xslDOM.load( xslURL );
 return xslDOM;
}

/** transform given XML DOM using given XSLT DOM
 * @return transformed XML @type String
 */
function XformDOM( xslDOM, xmlDOM ){
	return xmlDOM.transformNode( xslDOM );
}

/** transform given XML DOM using an XSLT retrieved from given URL
 * @return transformed XML @type String
 */
function XformURL( xslURL, xmlDOM ){
  return XformDOM( GetXslDOM(xslURL), xmlDOM );
}

The Gravy Framework

Documentation generated by JSDoc on Fri Mar 17 06:40:21 2006