﻿/**
 * @title javascripted page framework - ajax.js
 * @author huxiaodi(at)gmail.com
 * @cascadingauthor Matt Kruse <matt@ajaxtoolbox.com>,李华顺
 * @date 2009-03-03
 * @copyright this file is a subset of didyapp javascript framework. author(s) keep all rights received. 
 * while each class or function will have it's own copyright notice to be referer to.
 */
/**
 * @title didyapp.AjaxRequest
 * @author Matt Kruse <matt@ajaxtoolbox.com>, Huxiaodi <Huxiaodi(at)gmail.com>
 * @notice this js is rewrite from the prototype from 
 * http://www.jsorb.org/docs/js/overview-summary-ajaxrequest.js.html, 
 * whoes author is Matt Kruse <matt@ajaxtoolbox.com>
 * Matt Kruse's original copyright notice is as followed:
	// Author: Matt Kruse <matt@ajaxtoolbox.com>
	// WWW: http://www.AjaxToolbox.com/
	//
	// NOTICE: You may use this code for any purpose, commercial or
	// private, without any further permission from the author. You may
	// remove this notice from your final code if you wish, however it is
	// appreciated by the author if at least my web site address is kept.
	//
	// You may *NOT* re-distribute this code in any way except through its
	// use. That means, you can include it in your product, or your web
	// site, or any other form where the code is actually being used. You
	// may not put the plain javascript up on your site for download or
	// include it in your javascript libraries for download. 
	// If you wish to share this code with others, please just point them
	// to the URL instead.
	// Please DO NOT link directly to my .js files from your site. Copy
	// the files to your server and use them there. Thank you.
	// ===================================================================
	
 * Huxiaodi (me) has modified the code and added the following functions: 
 * a new messages driven model (EventRouter), a request and receiver 
 * wrapper (Worker), an Xml converter, an XSL Transformer.
 * This class requires components: Array(prototype extended), Hashtable, ER, Function(prototype extended), which are mostly like in file includes.js
 * SOME BEHAVIOR: The AjaxRequest will count the running threads of HttpRequest, if the num>0, 
 * it will try to set a element named "loading" display, if the num==0, it will try
 * to hide the "loading" element. you may put a loading picture in a "loading" element
 * anywhere in your web.
 */
var AjaxRequest=function(args){
	this.initialize(args);
	return this;
};
AjaxRequest.prototype={
	attachedObj:null,
	generateUniqueUrl : true,
	url : window.location.href,
	method : "GET",
	async : true,
	parameters : {},
	requestIndex : AjaxRequest.numAjaxRequests++,
	responseReceived : false,
	groupName : null,
	queryString : "",
	responseText : null,
	responseXML : null,
	status : null,
	statusText : null,
	aborted : false,
	_xmlHttpRequest : null,

	initialize:function(args){
		this.xmlHttpRequest = AjaxRequest.getXmlHttpRequest();
		if (this.xmlHttpRequest==null) {
			alert("no supported Requester");
		}
		this.xmlHttpRequest.onreadystatechange=this._onReadyStateChangeInternal.bind(this);

		this._onLoadingInternalHandled = false;
		this._onLoadedInternalHandled = false;
		this._onInteractiveInternalHandled = false;
		this._onCompleteInternalHandled = false;
		if(args){
			this.parameters={};
			this._handleArguments(args);
		}
	},
	getXmlHttpRequest:function(){
		return this.xmlHttpRequest;
	},
	getStatus :function(){
		return this.status;
	},
	getStatusText :function(){
		return  this.statusText;
	},
	getResponseText :function(){
		return  this.responseText;
	},
	getResponseXML :function(){
		return  this.responseXML;
	},
	_onReadyStateChangeInternal:function(){
		if (this!=null && this.xmlHttpRequest!=null){
			if (this.xmlHttpRequest.readyState==1) {
				this._onLoadingInternal();
			}
			if (this.xmlHttpRequest.readyState==2) {
				this._onLoadedInternal();
			}
			if (this.xmlHttpRequest.readyState==3) {
				this._onInteractiveInternal();
			}
			if (this.xmlHttpRequest.readyState==4) {
				this._onCompleteInternal();
			}
		}
	},
	_onReadyStateChange:function(eventType,param){
		var event=new Object();
		event.param=param;
		event.eventType=eventType;
		event.requests=AjaxRequest.numAjaxRequests;
		event.activeRequests=AjaxRequest.numActiveAjaxRequests;

		ER.trigger(this,event,eventType);
		ER.trigger(this,event,"onReadyStateChange");
		ER.trigger(AjaxRequest,event,"onReadyStateChange");
		ER.trigger(AjaxRequest,event,eventType);
		var loading=document.getElementById("loading");
		if(loading){

			loading.style.display=(AjaxRequest.numActiveAjaxRequests>0)?"":"none";
		}
	},
	_onLoadingInternal : function() {

		if (this._onLoadingInternalHandled) {
			return;
		}
		this._increaseNumActiveAjaxRequests();
		if (AjaxRequest.numActiveAjaxRequests==1 && typeof(window['AjaxRequestBegin'])=="function") {
			window.AjaxRequestBegin();
		}
		if (this.groupName!=null) {
			if (typeof(AjaxRequest.numActiveAjaxGroupRequests[this.groupName])=="undefined") {
				AjaxRequest.numActiveAjaxGroupRequests[this.groupName] = 0;
			}
			AjaxRequest.numActiveAjaxGroupRequests[this.groupName]++;
			if (AjaxRequest.numActiveAjaxGroupRequests[this.groupName]==1 ) {
				this._onReadyStateChange("onGroupBegin",this.groupName);
			}
		}
		this._onReadyStateChange("onLoading");
		this._onLoadingInternalHandled = true;
	},
	_onLoadedInternal : function() {

		if (this._onLoadedInternalHandled) {
			return;
		}
		/*
if(this.xmlHttpRequest.status==404){
this.onReadyStateChange("onError");
}*/
		else{
			this._onReadyStateChange("onLoaded");
		}
		this._onLoadedInternalHandled = true;
	},
	_onInteractiveInternal : function() {

		if (this._onInteractiveInternalHandled) {
			return;
		}
		this._onReadyStateChange("onInteractive");
		this._onInteractiveInternalHandled = true;
	},
	_onCompleteInternal : function() {

		if (this._onCompleteInternalHandled){
			return;
		}
		if(this.aborted) {
			this._onReadyStateChange("onAborted"); return;
		}

		this._onCompleteInternalHandled = true;
		this._onReadyStateChange("onLoading");
		this._reduceNumActiveAjaxRequests();

		if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
			window.AjaxRequestEnd(this.groupName);
		}
		if (this.groupName!=null) {
			AjaxRequest.numActiveAjaxGroupRequests[this.groupName]--;
			if (AjaxRequest.numActiveAjaxGroupRequests[this.groupName]==0) {
				this._onReadyStateChange("onGroupEnd",this.groupName);
			}
		}
		this.responseReceived = true;
		this.status = this.xmlHttpRequest.status;
		this.statusText = this.xmlHttpRequest.statusText;
		this.responseText = this.xmlHttpRequest.responseText;
		this.responseXML = this.xmlHttpRequest.responseXML;


		this._onReadyStateChange("onComplete");

		if (this.xmlHttpRequest.status==200) {
			this._onReadyStateChange("onSuccess");
		}
		else{
			this._onReadyStateChange("onError");
		}

		// Clean up so IE doesn't leak memory
		delete this.xmlHttpRequest['onreadystatechange'];
		this.xmlHttpRequest = null;
	},
	_increaseNumActiveAjaxRequests:function(){
		if(this.async){
			AjaxRequest.numActiveAjaxRequests++;
		}
	},
	_reduceNumActiveAjaxRequests:function(){
		if(this.async){
			AjaxRequest.numActiveAjaxRequests--;
		}
	},
	_onTimeoutInternal :function() {
		if (this!=null && this.xmlHttpRequest!=null && !this._onCompleteInternalHandled) {
			this.aborted = true;
			this.xmlHttpRequest.abort();
			this._reduceNumActiveAjaxRequests();
			if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
				window.AjaxRequestEnd(this.groupName);
			}
			if (this.groupName!=null) {
				AjaxRequest.numActiveAjaxGroupRequests[this.groupName]--;
				if (AjaxRequest.numActiveAjaxGroupRequests[this.groupName]==0 && typeof(this._onGroupEnd)=="function") {
					this._onReadyStateChange("onGroupEnd",this.groupName);
				}
			}

			this._onReadyStateChange("onTimeout");

			// Opera won't fire onreadystatechange after abort, but other browsers do.
			// So we can't rely on the onreadystate function getting called. Clean up here!
			delete this.xmlHttpRequest['onreadystatechange'];
			this.xmlHttpRequest = null;
		}
	},
	process : function() {

		if (this.xmlHttpRequest!=null) {
		
			// Some logic to get the real request URL
			if (this.generateUniqueUrl && this.method=="GET") {
			
				this.parameters["AjaxRequestUniqueId"] =new Date().getTime().toString();
				
			}
			var content = null; // For POST requests, to hold query string
			this.queryString="";
			for(var key in this.parameters){
				var value=this.parameters[key];
				if (this.queryString.length>0) {
					this.queryString += "&";
				}
				this.queryString += encodeURIComponent(key) + "=" + encodeURIComponent(encodeURIComponent(value));
			};
			if (this.method=="GET") {
				if (this.queryString.length>0) {
					this.url += ((this.url.indexOf("?")>-1)?"&":"?") + this.queryString;
				}
			}
			var method=(this.method=="GET")?"GET":"POST";
			this.xmlHttpRequest.open(method,this.url,this.async);
			if (this.method=="POST") {
			
				if (typeof(this.xmlHttpRequest.setRequestHeader)!="undefined") {
					this.xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
				}
				content = this.queryString;

			}
			if (this.timeout>0) {
				window.setTimeout(this._onTimeoutInternal.bind(this),this._timeout);
			}
			this.xmlHttpRequest.send(content);

		}
	},
	_handleArguments : function(args) {
		for (var i in args) {
			// If the AjaxRequest object doesn't have a property which was passed, treat it as a url parameter
			if (typeof(this[i])=="undefined") {
				this.parameters[i] = args[i];
			}
			else {
				this[i] = args[i];
			}
		}
	},
	abort:function(){
		if (this!=null && this.xmlHttpRequest!=null && !this._onCompleteInternalHandled) {
			this.aborted = true;
			this.xmlHttpRequest.abort();
			this._reduceNumActiveAjaxRequests();
			if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
				window.AjaxRequestEnd(this.groupName);
			}
			if (this.groupName!=null) {
				AjaxRequest.numActiveAjaxGroupRequests[this.groupName]--;
				if (AjaxRequest.numActiveAjaxGroupRequests[this.groupName]==0 && typeof(this._onGroupEnd)=="function") {
					this._onReadyStateChange("onGroupEnd",this.groupName);
				}
			}
			this._onReadyStateChange("onAbort");
			delete this.xmlHttpRequest['onreadystatechange'];
			this.xmlHttpRequest = null;
		}
	}
};
AjaxRequest.getXmlHttpRequest = function() {
	if (window.XMLHttpRequest) {
		return new window.XMLHttpRequest();
	}
	else if (window.ActiveXObject) {
		try {
			return new window.ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e) {
			try {
				return new window.ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (E) {
				return null;
			}
		}
	}
	else {
		return null;
	}
};
AjaxRequest.isActive = function() {
	return (AjaxRequest.numActiveAjaxRequests>0);
};
AjaxRequest.numActiveAjaxRequests = 0;
AjaxRequest.numActiveAjaxGroupRequests = new Object();
AjaxRequest.numAjaxRequests = 0;


/**
 * @title Didy Ajax Request Client Worker
 * @author huxiaodi(at)gmail.com
 * @desca wrapper of AjaxRequest
 * @usage
  var worker=new Worker("where_to_the_request");
  worker.onSuccess=function(request,xml,text){
  	//TODO;
  }
  var args={param1:value1,param2:value2};
  worker.request(args);
 * all the callback funtions are:
 * onSuccess,onError,onTimeout,onAbort,onComplete,onStateChange.
 * the parameter(s) of onSuccess are request,xml,text, for the rest functions is only request.
 * for multiple listeners situation, you can register them by: 
 * ER.addListener(worker,"onSuccess",callback1.asListener(this));
 * ER.addListener(worker,"onSuccess",callback2.asListener(this));
 */
var Worker=function(url){
	this.initialize(url);	
	this._request;
	return this;
};
Worker.prototype={
	//_request:null,
	//_servletUrl:null,
	initialize:function(servletUrl){
		if(servletUrl)
			this._servletUrl=servletUrl;
	},
	isReady:function(){
		return !!this._request;
	},
	abort:function(){
		if(this._request){
			this._request.abort();
		}
	},
	request:function(args){
		this._doRequest(args);
	},
	_doRequest:function(args){
		args=args||{};
		args.url=this._servletUrl;
		args.method="POST";
		this._request = new AjaxRequest(args);
		ER.addListener(this._request,"onReadyStateChange",this._onStateChangeHandler.asListener(this));
		this._request.process();
	},

	_onStateChangeHandler:function(event,eventType,observer,eventSource,params){
		
		var text=null,head=null,xml=null,json=null,error=null,status=event.eventType;
		var request=eventSource;
		text=request.responseText;
		if(status=="onSuccess"){	
			if(this.onSuccess)
			
			this.onSuccess(request,request.getResponseXML(),request.getResponseText());
		}
		if(event.eventType=="onError"){
			if(this.onError)
			this.onError(request);
		}
		if(event.eventType=="onTimeout"){
			if(this.onTimeout)
			this.onTimeout(request);
		}
		if(event.eventType=="onAborted"){
			if(this.onAbort)
			this.onAbort(request);
		}
		if(event.eventType=="onComplete"){
			if(this.onComplete)
			this.onComplete(request);
		}
		if(this.onStateChange)
		this.onStateChange(request);
		var e={
			request:request,
			status:status
		}
		ER.trigger(this,e,status);
		ER.trigger(this,e,"onStateChange");
	}
}

/**
 * parse string to xmlDom
 */
function parseXml(str){
  if(window.ActiveXObject){
    var rs = new ActiveXObject("microsoft.XMLDOM");
    result.loadXML(str);
  }
  else{
    var parser = new DOMParser();
    var rs = parser.parseFromString(str, "text/xml");
  }
  return rs;
}

/**
 * parse xmlDom to string
 */
function xml2String(xmlObject){
	try{
		var string = (new XMLSerializer()).serializeToString(xmlObject);
		return string;
	}catch(e){
		try{
		return xmlObject.xml;
		}catch(er){throw e;}
	}
}
/**
 * @title Didy XSL Transformer 1.0
 * @author huxiaodi
 */
var XSL=top.XSL={
	/**
     * @return transformed node
     */
	tranform:function(xml,xsl,params){
		params.base=_base;
			if(window.ActiveXObject){
			var template = new ActiveXObject("MSXML2.XSLTemplate");
			template.stylesheet = xslt;
			var proc = template.createProcessor();
			proc.input = xml;
			if (params != null) {
				for (var i=0;i<params.length;i++) {
					var param = params[i];
					proc.addParameter(param.name,param.value,"");
				}
			}
			proc.transform();
			return proc.output; 
		}
		else{
			var xsltProcessor=new XSLTProcessor();
			var ownerDocument = document.implementation.createDocument("", "doc", null);
			xsltProcessor.importStylesheet(xsl);
			for(var param in params){
				if(xsltProcessor.setParameter){
					xsltProcessor.setParameter(null,param,params[param]);
				}
				else if(xsltProcessor.addParameter){
					xsltProcessor.addParameter(param,params[param]);
				}
			}
			return xsltProcessor.transformToFragment(xml,document);
		}
	}
}


/**
 * @desca the follewing code is design to add some XML behaviors to MOZ browsers as IE style. 
 * @url http://www.cnblogs.com/huacn/archive/2007/07/23/javascript_firefox_xml_document_selectnodes.html
 * @author 李华顺
 */
if(document.implementation && document.implementation.createDocument){
    XMLDocument.prototype.loadXML = function(xmlString){
        var childNodes = this.childNodes;
        for (var i = childNodes.length - 1; i >= 0; i--)
            this.removeChild(childNodes[i]);

        var dp = new DOMParser();
        var newDOM = dp.parseFromString(xmlString, "text/xml");
        var newElt = this.importNode(newDOM.documentElement, true);
        this.appendChild(newElt);
    };

    // check for XPath implementation
    if( document.implementation.hasFeature("XPath", "3.0") ){
       // prototying the XMLDocument
       XMLDocument.prototype.selectNodes = function(cXPathString, xNode){
          if( !xNode ) { xNode = this; } 
          var oNSResolver = this.createNSResolver(this.documentElement)
          var aItems = this.evaluate(cXPathString, xNode, oNSResolver, 
                       XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
          var aResult = [];
          for( var i = 0; i < aItems.snapshotLength; i++)
          {
             aResult[i] =  aItems.snapshotItem(i);
          }
          return aResult;
       }

       // prototying the Element
       Element.prototype.selectNodes = function(cXPathString){
          if(this.ownerDocument.selectNodes){
             return this.ownerDocument.selectNodes(cXPathString, this);
          }
          else{throw "For XML Elements Only";}
       }
    }

    // check for XPath implementation
    if( document.implementation.hasFeature("XPath", "3.0") ){
       // prototying the XMLDocument
       XMLDocument.prototype.selectSingleNode = function(cXPathString, xNode){
          if( !xNode ) { xNode = this; } 
          var xItems = this.selectNodes(cXPathString, xNode);
          if( xItems.length > 0 ){
             return xItems[0];
          }
          else{
             return null;
          }
       }
       
       // prototying the Element
       Element.prototype.selectSingleNode = function(cXPathString){    
          if(this.ownerDocument.selectSingleNode){
             return this.ownerDocument.selectSingleNode(cXPathString, this);
          }
          else{throw "For XML Elements Only";}
       }
    }
}
