The Gravey Framework and RATS RIA

grvAJAX.js

Summary

Collection of AJAX support routines.

Version: 2.0

Requires:

Author: Bruce Wallace (PolyGlotInc.com)


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

Method Summary
static Object grvGetXslDOM( xslURL )
           retrieve XSL DOM from given URL
static String grvXformDOM( xslDOM, xmlDOM )
           transform given XML DOM using given XSLT DOM
static String grvXformURL( xslURL, xmlDOM )
           transform given XML DOM using an XSLT retrieved from given URL

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

/**
 * @file         grvAJAX.js
 * @fileoverview Collection of AJAX support routines.
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @requires     grvUtils.js
 * @requires     grvValidate.js
 * @version      2.0
 */

//////////////////////////////////////////////////////////////////
// GRAVEY LEXICAL CODING CONVENTIONS:
// (*) All private variables or functions start with "_"
// (*) All variables and functions start with a lowercase letter
// (*) All Classes start with an uppercase letter
// (*) All Class methods and instance variables start with lowercase
// (*) All Class "static" methods and variables start with uppercase
// (*) All constants start with "k"
// (*) All global variables start with "g"
// (*) All event handler functions start with "on"
//
// (*) All Gravey utility global variables start with "gGrv"
// (*) All Gravey MVC     global variables start with "gMVC"
// (*) All Gravey EDO     global variables start with "gEDO"
// (*) All Gravey utility functions start with "grv"
// (*) All Gravey MVC     functions start with "mvc"
// (*) All Gravey MVC event handler functions start with "onMVC"
// (*) All Gravey EDO event handler functions start with "onEDO"
// (*) All Gravey MVC classes start with "MVC"
// (*) All Gravey EDO classes start with "EDO"
//////////////////////////////////////////////////////////////////

Class(XMLreq,["url"]);
/**
 * @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.
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function XMLreq()
{
	/**
	 * @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)
	 */
	this.konstructor = function( url, userCallbackFunction, optPostData, optWaitFlag, optTestFlag, optSuppressLaunchFlag )
	{
		// initialize instance data
		this.name   = "XMLreq" + grvTimestamp(); //mostly unique name
		this.url    = url;
		this.post   = optPostData;
		this.test   = grvIsUndefined(optTestFlag) ? _kGrvAjaxTestData : optTestFlag;
		this.async  = optWaitFlag?false:true;
		this.usercb = userCallbackFunction || function(){ grvError("Unhandled Reply=["+this.getResponseText()+"]"); };
		this.cb     = new Function( "grvGetGlobalVar(\""+ this.name +"\").cbwrapper()" );
	
		// save reference to "this" in page-level variable (to be retrieved in callback)
		grvSetGlobalVar( this.name, this );
		if (!optSuppressLaunchFlag) this.launch();
	}

	/** method to return "this" as a human-readable string @type String */
	this.toString = function(){ return grvObjectToString(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
	 */
	this.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.
	 */ 
	this.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
			grvClearGlobalVar( 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)
	    	 grvBreak("DEBUG:zero xml status! msg["+ this.obj.statusText +"] for ["+this.name+"]");
		else grvError(   "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>
	 */
	this.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
	 */
	this.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
	 */
	this.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
	 * @throws ErrMsg on fatal error
	 */
	this.xform =  function( xslURL ) {
		return grvXformURL( xslURL, this.getResponseXML() );
	}

	/** return the output from transforming the response XML
	 * with the XSL in the specified XSL DOM
	 * @type String
	 * @throws ErrMsg on fatal error
	 */
	this.xform2 = function( xslDOM ) {
		return grvXformDOM( xslDOM, this.getResponseXML() );
	}
}

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

/** retrieve XSL DOM from given URL
 * @return XSL DOM @type Object
 */
function grvGetXslDOM( xslURL )
{
    // load XSLT stylesheet document
    var xslDOM = new ActiveXObject("Msxml2.DOMDocument");
        xslDOM.async = false;
        xslDOM.load( xslURL );
	if (xslDOM.parseError.errorCode != 0) {
		var myErr = xmlDoc.parseError;
		grvError("Error loading["+xslURL+"]: " + myErr.reason);
	}
	return xslDOM;
}

/** transform given XML DOM using given XSLT DOM
 * @return transformed XML @type String
 * @throws ErrMsg on fatal error
 */
function grvXformDOM( xslDOM, xmlDOM ){
	if (xslDOM.parseError.errorCode != 0)
	  throw "XSL was not successfully loaded; errcode="+xslDOM.parseError.errorCode;
	return xmlDOM.transformNode( xslDOM );
}

/** transform given XML DOM using an XSLT retrieved from given URL
 * @return transformed XML @type String
 * @throws ErrMsg on fatal error
 */
function grvXformURL( xslURL, xmlDOM ){
  return grvXformDOM( grvGetXslDOM(xslURL), xmlDOM );
}

The Gravey Framework and RATS RIA

Documentation generated by JSDoc on Sat Dec 8 21:52:02 2007