

/*

AJAX.JS

Copyright 2008, Province of British Columbia, all rights reserved.
This material is owned by the Ministry of Forests and Range and
the Government of British Columbia and is protected by copyright
law. It may not be reproduced or redistributed without the prior
written permission of the Province of British Columbia.



USE:

var z = new AJAX(); z.preGo({
	xml:'test.xml',
	xsl:'test.xsl?js=z',
	dest:document.getElementById('test')
})

reference back to the script within XSL, reference the "js" variable.
this provides tw functions at the moment, DELTA and SORTER
  - pass 1 or -1. - moves start up or down, based on list size
  - pass field.   - changes sort order and/or sort field.

e.g.

<xsl:attribute name="onClick"><xsl:value-of select="concat($JS,'.delta(&quot;-1&quot;);')"/></xsl:attribute>
<xsl:attribute name="onclick"><xsl:value-of select="concat($JS,'.sorter(&quot;title&quot;)')" /></xsl:attribute>



You can use any number of ajax scripts on one page.

*/


function thrower( msg ) { var DEBUG=0; DEBUG && alert(msg); }
var myAJAX = {};


function Req( _args ) {

	this.processor=null;
	this.id=null;
	this.xml=null;
	this.xsl=null;
	this.method=null;
	this.dest=null;
	this.query={};
	this.param={};

	// get the intial parameters
	var arg; for( arg in _args ) this[arg] = _args[arg];

	if( !this.dest ) this.dest = document.createElement("DIV");
	if( !this.method ) this.method = "GET";
	if( !this.id ) {
		var tmpId = String(Math.random())
		this.id = "A"+tmpId.substring( tmpId.indexOf(".")+1);
	}

	// parse the QS into the assorted collections
	if( this.xsl ) this.xsl = parseQS (this.xsl, this.setParam, this )
	if( this.xml ) this.xml = parseQS (this.xml, this.setQuery, this )

	// set up default sort paramters if not set from xml/xsl params.
	if(!this.getQuery("sortField")) this.setQuery("sortField", "datePosted");
	if(!this.getQuery("sortOrder")) this.setQuery("sortOrder", "desc");
	if(!this.getQuery("searchSize")) this.setQuery("searchSize", "5");
	if(!this.getQuery("start")) this.setQuery("start", "1");
	if( this.getParam("js")) {
		this.setParam("js", this.getParam("js") +".requests['" + this.id + "']" );
	}
	return
}

Req.prototype = {

	// generic functions for moving around the data via query/params
 	delta : function(direction) {this.setQuery("start",String(parseInt(this.getQuery("start"))+((parseInt(direction)*-1)*parseInt(this.getParam('searchSize'))))); this.ajax.go(this.id); }
	,sorter : function(newsort) {this.setQuery("sortField",newsort);this.setQuery("sortOrder",this.getQuery("sortOrder")=="ascending"?"descending":"ascending" ); this.ajax.go(this.id); }

	// functions for handling parameters (XSL) and querystrings (XML)
	,setParam : function(_id,_value) {this.param[_id.toUpperCase()] =_value;}
	,setQuery : function(_id,_value) {this.query[_id.toUpperCase()] =_value;}
	,getParam : function(_id) { if( _id ) return this.param[_id.toUpperCase()]; var str="?"; for( var x in this.param ) str+= (x)+"="+this.param[x]+"&"; return str.substr(0, str.length-1);}
	,getQuery : function(_id) { if( _id ) return this.query[_id.toUpperCase()]; var str="?"; for( var x in this.query ) str+= (x)+"="+this.query[x]+"&"; return str.substr(0, str.length-1);}
	,remParam : function(_id) { this.param[_id.toUpperCase()]=null; this.updParam(); }
	,remQuery : function(_id) { this.query[_id.toUpperCase()]=null; }
	,updParam : function() { if( !this.processor ) return; for( x in this.param ){
			if( window.ActiveXObject ) { this.processor.addParameter( x,this.param[x] ); }
			else { if( this.param[x] == null ) { this.processor.removeParameter(null, _id); }
			else { this.processor.setParameter(null, x, this.param[x]); } }
	}}
}




function AJAX( _args ) {
	this.requests={};
	this.xhrids={};

	if( arguments.length == 1 ) {

		// preGo is called when required args are passed to AJAX object
		// make go calls for both the XSL (async FALSE) and XML (async TRUE)

		var args={}, arg; for( arg in _args ) args[arg] = _args[arg]; arg=null;
		if( !args.id ) args.id = Math.random();
		if(args.xml&&args.xsl&&args.dest) this.preGo(args);

		return;

	} else if( arguments.length == 0 ) {

		// BACKWARDS COMPATABILITY FOR EXISTING _xml OBJECT IN AJAX.JS
		// will not pass parameters; "init" does this; "update" maps to "Go"
		// the rest of the backwards-compat code is below AJAX_2 prototype.

		return;

	}

	var str="{dest:document.getElementById('[id]'),xml:'anXMLDataFile.xml',xsl:'anXSLStyleSheet.xsl',qs:{'SubjectId':'12','TypeId':'3'}}"
	prompt("There was an error with your arguments parameter\n Please copy this prompt text for correct JSON format\nNote that QS is optional", str );

}


AJAX.prototype = {

	preGo : function( _args ) {

		var tmp = _args; tmp["ajax"] = this;
		if( !tmp["id"] ) tmp["id"] = Math.random();
		this.requests [ tmp.id ] = new Req(tmp);
		this.go( tmp.id );
	}

	,go : function( _id, _argOverwrite ) {
		if( arguments.length == 0 ) return;

		/* generic handlers - these are basic handlers for XSL and XML
			XSL:  includes hookups to create processor from input, calls go
			XML:  requires processor, uses request input and transforms
		note that the second parameter can be passed to get full control */

		var generic_XSL = {method:"GET",async:false,callback:function(request,id){this.requests[id].processor=this.createProcessor(request.responseXML); this.loadParams(id,request.responseXML); this.go( id );}}
		  , generic_XML = {method:"GET",async:true,callback:function(request,id){this.requests[id].processor.input=request.responseXML;this.transform(id,{output:'DOM'});}};

		var req = this.requests[_id],
			xhr = this.getXHR( _id ),
			tmp;

		if(_argOverwrite) { tmp = _argOverwrite }
		else if(!req.processor) {tmp = generic_XSL; tmp.url = req.xsl }
		else {tmp = generic_XML; tmp.url = req.xml + req.getQuery()}

		xhr.callback = tmp.callback;

		if( !tmp.data ) { tmp.data = req.getQuery(); }
		if( tmp.method.toUpperCase() == "GET") tmp.data=null;
		if( typeof(tmp.async)=="undefined" ) tmp.async = true;

		if(window.ActiveXObject){
			xhr.request.open( tmp.method, tmp.url, tmp.async);
			if( tmp.async ) xhr.request.onreadystatechange=function(){finishRequest(xhr.id)}
		}else{
			if( tmp.async ) xhr.request.onreadystatechange=function(){finishRequest(xhr.id)}
			xhr.request.open( tmp.method, tmp.url, tmp.async);
		}

		xhr.inuse = true;
		if( tmp.method.toUpperCase() == "POST") {
			if(!tmp.data ){ thrower("must send data on post requests"); return false; }
			else { xhr.request.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); }
		}
		xhr.request.send(xhr.data);
		if( tmp.async == false ) {
			xhr.inuse=false;
			tmp.callback.call( this, xhr.request, _id );
		}
	}

	,getXHR : function( _id ) {
		var t=null; for( var r in this.xhrids ) {
			var tmpId = this.xhrids[r].id;
			if( myAJAX[tmpId].inuse == false ) {
				if(!t){ t = myAJAX[tmpId]; t.reqId=_id; }
				else{ delete myAJAX[tmpId]; delete this.xhrids[tmpId]; }
			}
		}
		if(!t) { t = {};
			t["ajax"] = this;
			t["request"] = window.ActiveXObject ? createMSXML("XMLHTTPRequest") : new XMLHttpRequest();
			t["inuse"] = false;
			t["id"] = Math.random()
			t["reqId"] = _id
			this.xhrids[ t["id"] ] = t;
			myAJAX[ t["id"] ] = t;
		} else {
			thrower("returning xhr");
		}
		return t;
	}

	,transform:function(id, _args) {
		var a, args={}; for( var arg in _args ) args[arg] = _args[arg]; arg=null;
		if( !(this.requests[id].processor && args.output )) return false;

		this.requests[id].updParam();

		switch( args.output ) {
			case "JSON": a="";
				if( window.ActiveXObject ) { this.processor.transform(); a = this.processor.output; }
				else { a = mozXMLSerializer(this.processor.transformToFragment( this.processor.input, document )); }
			break;

			case "DOM": a = document.createElement("DIV");
				if( window.ActiveXObject ) { this.requests[id].processor.transform(); a.innerHTML = this.requests[id].processor.output; }
				else { a.appendChild( this.requests[id].processor.transformToFragment( this.requests[id].processor.input, document ) ); }
				this.requests[id].dest.parentNode.replaceChild( a, this.requests[id].dest); this.requests[id].dest = a; go_decoding();
			break;
		}
		if( args.callback ) args.callback.call( this, a )
	}

	,createProcessor:function(xmlDoc) {
		if( window.ActiveXObject ) {
			var xslFreeThread = createMSXML("FreeThreadedDOMDocument");
			xslFreeThread.load (xmlDoc);
			xslFreeThread.resolveExternals = true;
			var xslTemplate = createMSXML("XSLTemplate");
			xslTemplate.stylesheet = xslFreeThread;
			return xslTemplate.createProcessor();
		}
		else {
			var a = new XSLTProcessor();
			a.importStylesheet( xmlDoc );
			return a;
		}
	}

	,loadParams:function(reqId, xmlDoc) {
		var paramQuery="//xsl:param[ count(@select)> 0]/@*"

		if( window.ActiveXObject ) {
			xmlDoc.setProperty("SelectionLanguage", "XPath");
			xmlDoc.setProperty("SelectionNamespaces", "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
		}

		var z = window.ActiveXObject ? xmlDoc.selectNodes(paramQuery) : mozSelectNodes( xmlDoc, paramQuery);
		var t = window.ActiveXObject ? z.nextNode() : z.iterateNext();
		while(t) {
			var id = t.nodeValue; t = window.ActiveXObject ? z.nextNode() : z.iterateNext();
			var val = t.nodeValue; t = window.ActiveXObject ? z.nextNode() : z.iterateNext();
			this.requests[reqId].setParam( id, val.replace("'", "", "gi") );
		}
	}

}



/*
	the way the major browsers process the onReadyStateChange event leaves
	something to be desired.  In FF, |this| is bumped down to window-scope

	NOTE: if IE is using the XHR for multiple requests, onReadyStateChange must
	be attached to the function AFTER the open call is made. vice versa in FF.

	due to this, a global object  myAJAX  is available. this stores a collection
	of references, each storying: the AJAX object, XHR and the  Id of the request
	each is an object referenced by an xhr_id ( a Math.random() call )
	when a request comes in, set the inuse to false (so it can be reused)
	and call the callback function, passing the finished XHR & reqId as params

*/

function finishRequest( xhr_id ) {
	var me = myAJAX[xhr_id];
	if( me.request.readyState == 4 ) {
	if( me.request.status == 200 ) {

		// request is finished, callback.
		me.inuse = false; thrower('finished request has come in');
		if( me.callback ) me.callback.call( me.ajax, me.request, me.reqId );

	} else { /* http error, not 200 OR 0 status, not running on web server */
		thrower("there was a problem loading:\n" + me.url
			 +"\n\nThe following error code was returned:"
			 +"\n\nERROR #" + me.request.status + ": " + me.request.statusText );
		return false;
	}
	} else { return false; }
}

/* GENERAL UTILITY FUNCTIONS

	parseQS
	 - for parsing querystring value pairs
	 - values in "input" go to "func", pushing "scope" as |this| in func
	 - returns either full input or everything after the ?, if included

	addEvent/removeEvent
	 - x-browser event handlers
*/

function parseQS( i, f, scope ) {
	if(i.indexOf("&amp;")>0){i=i.replace(/&amp;/g,"&");}
	if(i.indexOf("?")>0){var a=i.substr(i.indexOf("?")+1).split("&");
	for(x=0;a[x];x++){var tmp=a[x].split("=");f.call(scope,tmp[0],tmp[1]);}
	return i.substr(0,i.indexOf("?"));}else{return i}
}

function addEvent(obj, evType, fn, useCapture){
  if (obj.addEventListener){ obj.addEventListener(evType, fn, useCapture); return true; }
  else if (obj.attachEvent){ var r = obj.attachEvent("on"+evType, fn); return r; }
  else { obj['on'+evType] = fn; return false; }
}

function removeEvent(obj, evType, fn, useCapture){
  if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true; }
  else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r; }
  else { obj['on'+evType] = null; return false; }
}



/*
	MOZ UTILITY FUNCTIONS

	 - xml serializer - turns an xml document into a String

	 - selectNodes - takes xpath and returns XmlNodeList.
	   ( includes namespace resolver )
*/


function mozSerialize( xmlDoc ) { return new XMLSerializer().serializeToString(xmlDoc);}
function mozSelectNodes( xmlDoc, strXPath ) { return xmlDoc.evaluate( strXPath, xmlDoc, xmlDoc.createNSResolver( xmlDoc.ownerDocument == null ? xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement), XPathResult.ANY_TYPE, null ); }


/*
	IE UTILITY FUNCTIONS
	 createMSXML - creates objects used in MSXML -- see http://support.microsoft.com/kb/269238

		MSXML 6.0 -- latest version, M$ recommends to use this version of verionless
		-- but both versions 4/5 ARE still good and generally more used than 6.0

		MSXML 5.0 -- ships w/ Office 2003.  generally be superceded by version 6.
		BCGOV standard is 5.0 due to office and the lack of VISTA / IE7

		MSXML 4.0 -- a little older that 5.0.  ive heard this is shpped with
		some third party controls -- or WinXP SP2.  this will be used if no Office.

		MSXML 3.0 and version-less -- load last MSXML version installed in replace mode
		generally 3.0 but also 2.4, 2.0 and 1.0.  M$ says create v6, if no, versionless
		... BUT:
			v.3 doesn't support DISABLE OUTPUT ESCAPING
			v.3 requires absolute urls in XSL:Include's
		assorted scripts have been re-written to accomodate this.  version 3 sucks.


		just in case -- if the user doesn't have MSXML2, I don't know what to say.
		this doesn't support any XSL processing, so the user is basically SOL
		i'm not sure if this code will ever run -- we're talking pre IE4, pre win.98, etc.
		if user gets here, they will get a warning about using old COM controls.
*/


function createMSXML( _type ) {

	var v, tmp, vMSXML = {
		 '6':{
			'DOMDocument'             :'Msxml2.DOMDocument.6.0',
			'FreeThreadedDOMDocument' :'Msxml2.FreeThreadedDOMDocument.6.0',
			'XMLHTTPRequest'          :'Msxml2.XMLHTTP.6.0',
			'XSLTemplate'             :'Msxml2.XSLTemplate.6.0'
		}
		,'5':{
			'DOMDocument'             :'Msxml2.DOMDocument.5.0',
			'FreeThreadedDOMDocument' :'Msxml2.FreeThreadedDOMDocument.5.0',
			'XMLHTTPRequest'          :'Msxml2.XMLHTTP.5.0',
			'XSLTemplate'             :'Msxml2.XSLTemplate.5.0'
		}
		,'4':{
			'DOMDocument'             :'Msxml2.DOMDocument.4.0',
			'FreeThreadedDOMDocument' :'Msxml2.FreeThreadedDOMDocument.4.0',
			'XMLHTTPRequest'          :'Msxml2.XMLHTTP.4.0',
			'XSLTemplate'             :'Msxml2.XSLTemplate.4.0'
		}
		,'3':{
			"DOMDocument"             :'Msxml2.DOMDocument',
			"FreeThreadedDOMDocument" :'Msxml2.FreeThreadedDOMDocument',
			"XMLHTTPRequest"          :'Msxml2.XMLHTTP',
			"XSLTemplate"             :'Msxml2.XSLTemplate'
		}
		,'0':{
			"DOMDocument"             :'Microsoft.XMLDOM',
			"FreeThreadedDOMDocument" :'Microsoft.FreeThreadedXMLDOM'
		}
	}

	for( v in vMSXML ) { if( vMSXML[v][_type] ) {
	try{ tmp = new ActiveXObject(vMSXML[v][_type]) }
	catch (e) { continue; } break; } }

	if( tmp ) { if( v == "0" ) { thrower('WARNING:\n\n Using Microsoft COM base for XML processing.\n This may or may not work. Please upgrade your browser'); }
	return tmp; } else { thrower('Invalid Object:\n' + _type + " (MSXML v." +v+ ")"); }
}





// START BACKWARDS COMPATABILITY for "_xml"
function _xml() { this.ajaxStub = null; }
_xml.prototype = AJAX.prototype;
_xml.prototype['init'] = function( _xml, _xsl, _parent, _name ) {
	this.ajaxStub = new AJAX({ dest:_parent, xml:_xml, xsl:_xsl });
}
// END BACKWARDS COMPATABILITY





var DEBUG=0;
var is_decoding=false;

function complaining (s) { DEBUG && alert(s);  return new Error(s,s); }
function mozDOE () {

	// Time-stamp: "2006-05-17 22:06:46 ADT" sburke@cpan.org

	// A workaround for XSL-to-XHTML systems that don't
	//  implement XSL 'disable-output-escaping="yes"'.
	//
	// sburke@cpan.org, Sean M. Burke.
	//  - I hereby release this JavaScript code into the public domain.

	/* thanks sean! -- tsw */

	if(!( document.getElementById && document.getElementsByName ))
		throw complaining("Your browser is too old to render this page properly." +
		                  " Please consider going to mozilla.org to upgrade.");

	var d = document.getElementById("cometestme");
	if(!d) { throw complaining("Can't find an id='cometestme' element?"); }
	else if(!('textContent' in d)) {
		// It's a browser with a halfassed DOM implementation (like IE6)
		// that doesn't implement textContent!  Assume that if it's that
		// dumb, it probably doesn't implement disable-content-encoding.
		   /*tsw - good thing it supports DOE or we'd be completely SOL */
	} else {
		var ampy = d.textContent;

		DEBUG && alert("Got " + (ampy));
		if(ampy == undefined) throw complaining("'cometestme' element has undefined text content?!");
		if(ampy == ''       ) throw complaining("'cometestme' element has empty text content?!"    );
		if(ampy == "\x26"	) { is_decoding =  true; }
		else if (ampy == "\x26amp;" ) { is_decoding = false; }
		else { throw complaining('Insane value: "' + ampy + '"!'); }
	}

	var msg =  !(is_decoding) ? "I can't tell whether the XSL processor supports disable-content-encoding!D" :
		        (is_decoding) ? "The XSL processor DOES support disable-content-encoding"
		                      : "The XSL processor does NOT support disable-content-encoding";
	DEBUG && alert(msg);

	if(!is_decoding) { DEBUG && alert("No work needs doing -- already decoded!"); return false; }
	var to_decode = document.getElementsByName('decodeme');

	if(!( to_decode && to_decode.length )) {
		DEBUG && alert("No work needs doing -- no elements to decode!");
		return false;
	}


	if(! (( "innerHTML" in to_decode[0]) &&  ("textContent" in to_decode[0])) )
		throw complaining(
			"Your JavaScript version doesn't implement DOM properly enough to "+
			"show this page correctly. Consider going to mozilla.org to upgrade.");

		var s;
		for(var i = to_decode.length - 1; i >= 0; i--) {s = to_decode[i].textContent;
			if( ! (s== undefined||(s.indexOf('&')==-1&&s.indexOf('<')==-1))) {

			to_decode[i].innerHTML = s; // that's the magic
		}
		}
	return true;
} function go_decoding () { mozDOE() }

//End