/* --------------------------------------------------------------------------------------------------------------
 *	API:			JSMX (JavaScript MX) - Universal Ajax API for ColdFusion, PHP, .NET, or anything other language.
 *	AUTHOR: 		Todd Kingham [todd@lalabird.com] with contributions by Jan Jannek [jan.jannek@Cetecom.de] and Yin Zhao [bugz_podder@yahoo.com]
 *	CREATED:		8.21.2005
 *	VERSION:		2.6.3
 *	DESCRIPTION:	This API uses XMLHttpRequest to post/get data from a ColdFusion interface.
 *					The CFC's/CFM's will return a string representation of a JS variable: response_param.
 *					The "onreadystatechange event handler" will eval() the string into a JS variable 
 *					and pass the value back to the "return function". To Download a full copy of the sample 
 *					application visit: http://www.lalabird.com/JSMX/?fa=JSMX.downloads
 *
 *	HISTORY:		2.0.0:	Todd: Scripted Out Original Version
 *					2.1.0:	Todd: Modified for Download
 *					2.2.0:	Todd: Modified the firstWord() function to be backward compatable with
 *								  CF5 and to be more stable all-around.
 *					2.3.0:	Todd: Added "wait div" functionality
 *					2.4.0:	Todd: XML!!!! Now JSMX will allow you to pass XML Documents to the API in
 *								  addition to the original JavaScript method.
 *					2.4.1:	Jan:  2006-02-16, XMLHTTP requests can now handle more than one request at once. By placing the onreadystatechange event as a local variable inside the actual http() function.
 *							Jan:  Added fix for strange IE bug that returned Header Info.
 *							Todd: Added the jsmx object to allow users to override defaults and set custom "async", "wait" and "error" methods
 *					2.5.0:	Todd: Added JSON Support! So now you can pass JavaScript, XML, or JSON.
 *					2.5.1:	Todd: Version 2.5.0 was premature. Needed to fix an eval() bug when I introduced JSON.
 *					2.5.2:	Todd: Fixed a bug in the onreadystatechange. Based on the order you call the event handler... "State Change 1" gets called twice. Added code to only process code inside 'CASE 1:' once
 *					2.5.3:	Todd: Fixed a bug in the try/catch of the parser by placing the callback() call within the try/catch statement. This caused errors in the callback function to be "masked" and appear as "parsing errors", even when the parse was successful.
 *					2.6.0: 	Todd: Added WDDX Parser! Now you can return WDDX Strings as well.
 *					2.6.1:	Todd: Streamlined the ClassicMode and JSON parser into one function.
 *					2.6.2:	Todd: Replaced ParseInt() with ParseFloat() in the my WDDX Parser.
 *							Yin: _escape_utf8() to allow UTF-8 Chars. (modified from Cal Henderson's <cal@iamcal.com> version)
 *					2.6.3:	Todd: _escape_utf8() was choking on CR+LF: chr(13) && chr(10) ... modified function to correct problem.
 *
 *
 *	LICENSE:		THIS IS AN OPEN SOURCE API. YOU ARE FREE TO USE THIS API IN ANY APPLICATION,
 *               	TO COPY IT OR MODIFY THE FUNCTIONS FOR YOUR OWN NEEDS, AS LONG THIS HEADER INFORMATION
 *              	 	REMAINS IN TACT AND YOU DON'T CHARGE ANY MONEY FOR IT. USE THIS API AT YOUR OWN
 *               	RISK. NO WARRANTY IS EXPRESSED OR IMPLIED, AND NO LIABILITY ASSUMED FOR THE RESULT OF
 *               	USING THIS API.
 *
 *               	THIS API IS LICENSED UNDER THE CREATIVE COMMONS ATTRIBUTION-SHAREALIKE LICENSE.
 *               	FOR THE FULL LICENSE TEXT PLEASE VISIT: http://creativecommons.org/licenses/by-sa/2.5/
 *
-----------------------------------------------------------------------------------------------------------------*/
// UNCOMMENT THE FOLLOWING LINE IF YOU WILL BE RETURNING QUERY OBJECTS. (note: you may need to point the SRC to an alternate location.
/*document.writeln('<SCRIPT TYPE="text/javascript" LANGUAGE="JavaScript" SRC="/CFIDE/scripts/wddx.js"></SCRIPT >');*/

var jsmx = new jsmxConstructor();
function jsmxConstructor(){
    this.isJSMX = true;
    this.async = true;
    this.debug = false;
    this.waitDiv = 'JSMX_loading';
    this.http = http;
    this.onWait = _popWait;
    this.onWaitEnd = _killWait;
    this.onError = _onError;
}
// perform the XMLHttpRequest();
function http(verb,url,cb,q) {
    var self = (this.isJSMX) ? this : jsmx ;
    //reference our arguments
    var qryStr = (!q) ? '' : _toQueryString(q);
    var calledOnce = false; //this is to prevent a bug in onreadystatechange... "state 1" gets called twice.
    try{//this should work for most modern browsers excluding: IE Mac
        var xhr = ( window.XMLHttpRequest ) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP") ;
        xhr.onreadystatechange = function(){
            switch(xhr.readyState){
                case 1: 
                    if(!calledOnce){ 
                        self.onWait(self.waitDiv); 
                        calledOnce = true;
                    } 	break;
                case 2: break;
                case 3: break;
                case 4:
                    self.onWaitEnd(self.waitDiv);
                    if ( xhr.status == 200 ){// only if "OK"
                        var success = true;
                        try{
                            var rObj = _parseResponse( xhr );
                        }catch(e){ 
                            self.onError(xhr,self,1);
                            success = false;
                        }
                        if(success){ cb( rObj ); }
                    }else{
                        self.onError(xhr,self,2);
                    }
                    delete xhr; //clean this function from memory once we re done with it.
                    break;
            }
        };
        xhr.open( verb , _noCache(url) , self.async );
        if(verb.toLowerCase() == 'post') { xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); }
        xhr.send(qryStr);
    }catch(e){
        self.onError(xhr,self,3);
    }
}

/*--- BEGIN: RESPONSE PARSING FUNCTIONS ---*/
function _parseResponse($$){
    var str = _cleanString($$.responseText);
    var xml = $$.responseXML;
    //FIRST TRY IT AS XML
    if(xml != null && xml.childNodes.length){ return xml; } 
    //NEXT TRY IT AS WDDX
    if(str.indexOf("<wddxPacket") == 0){ return _parseWDDX(str); }
    //NEXT TRY IT AS JSON
    try{ return eval('('+str+')'); }
    //NEXT TRY IT AS JavaScript
    catch(e){ 
        // return _parseJS(str); 
        return str;
    }
}
// jan.jannek@cetecom.de, 2006-02-16, weird error: some IEs show the responseText followed by the complete response (header and body again) 
function _cleanString(str){ 
    //Left Trim
    var rex = /\S/i;
    str = str.substring(str.search(rex),str.length);
    
    var i = str.indexOf("HTTP/1");
    if (i > -1) {
        str = str.substring(i, str.length);
        i = str.indexOf(String.fromCharCode(13, 10, 13, 10));
        if (i > -1) { str = str.substring(i + 2, str.length); }
    }
    return str;	
}
function _parseJS(str){ 
    eval(str);
    var r = eval(str.split('=')[0].replace(/\s/g,''));
    return r;
}
function _parseWDDX(str){ var wddx = xmlStr2Doc(str); var data = wddx.getElementsByTagName("data"); return _parseWDDXnode(data[0].firstChild); } function xmlStr2Doc(str){ var xml; if(typeof(DOMParser) == 'undefined'){ xml=new ActiveXObject("Microsoft.XMLDOM"); xml.async="false"; xml.loadXML(str); }else{ var domParser = new DOMParser(); xml = domParser.parseFromString(str, 'application/xml'); } return xml; } function _parseWDDXnode(n){ var val; switch(n.tagName){ case 'string': val = _parseWDDXstring(n); break; case 'number': val = parseFloat(n.firstChild.data); break; case 'boolean': val = n.getAttribute('value'); break; case 'dateTime': val = Date(n.firstChild.data); break; case 'array': val = _parseWDDXarray(n); break; case 'struct': val = _parseWDDXstruct(n); break; case 'recordset': val = _parseWDDXrecordset(n); break; case 'binary': val = n.firstChild.data; break; case 'char': val = _parseWDDXchar(n);; break; case 'null': val = ''; break; default: val = n.tagName; break; } return val; } function _parseWDDXstring(node){ var items = node.childNodes; var str = ''; for(var x=0;x < items.length;x++){ if(typeof(items[x].data) != 'undefined') str += items[x].data; else str += _parseWDDXnode(items[x]); } return str; } function _parseWDDXchar(node){ switch(node.getAttribute('code')){ case '0d': return '\r'; case '0c': return '\f'; case '0a': return '\n'; case '09': return '\t'; } } function _parseWDDXarray(node){ var items = node.childNodes; var arr = new Array(); for(var i=0;i < items.length;i++){ arr[i] = _parseWDDXnode(items[i]); } return arr; } function _parseWDDXstruct(node){ var items = node.childNodes; var obj = new Object(); for(var i=0;i < items.length;i++){ obj[items[i].getAttribute('name').toLowerCase()] = _parseWDDXnode(items[i].childNodes[0]); } return obj; } function _parseWDDXrecordset(node){ var qry = new Object(); var fields = node.getElementsByTagName("field"); var items; var dataType; var values; for(var x = 0; x < fields.length; x++){ items = fields[x].childNodes; values = new Array(); for(var i = 0; i < items.length; i++){ values[values.length] = _parseWDDXnode(items[i]); } qry[fields[x].getAttribute('name').toLowerCase()] = values; } return qry; }
/*--- END: RESPONSE PARSING FUNCTIONS ---*/


/*--- BEGIN: REQUEST PARAMETER FUNCTIONS ---*/
function _toQueryString(obj){
    //determine the variable type
    if(typeof(obj) == 'string') { return obj; }
    if(typeof(obj) == 'object'){
        if(typeof obj.elements == 'undefined') {return _object2queryString(obj); }//It's an Object()!
        else{ return _form2queryString(obj); }//It's a form!
    }	
}	
function _object2queryString(obj){
    var ar = new Array();
    for(x in obj){ ar[ar.length] = _escape_utf8(x)+'='+_escape_utf8(obj[x]); }
    return ar.join('&');
}	
function _form2queryString(form){
    var obj = new Object();
    var ar = new Array();
    for(var i=0;i < form.elements.length;i++){
        try {
            elm = form.elements[i];
            nm = elm.name;
            if(nm != ''){
                switch(elm.type.split('-')[0]){
                    case "select":
                        for(var s=0;s < elm.options.length;s++){
                            if(elm.options[s].selected){
                                if(typeof(obj[nm]) == 'undefined'){ obj[nm] = new Array(); }
                                obj[nm][obj[nm].length] = _escape_utf8(elm.options[s].value);
                            }	
                        }
                        break;						
                    case "radio":
                        if(elm.checked){
                            if(typeof(obj[nm]) == 'undefined'){ obj[nm] = new Array(); }
                            obj[nm][obj[nm].length] = _escape_utf8(elm.value);
                        }	
                        break;						
                    case "checkbox":
                        if(elm.checked){
                            if(typeof(obj[nm]) == 'undefined'){ obj[nm] = new Array(); }
                            obj[nm][obj[nm].length] = _escape_utf8(elm.value);
                        }	
                        break;						
                    default:
                        if(typeof(obj[nm]) == 'undefined'){ obj[nm] = new Array(); }
                        obj[nm][obj[nm].length] = _escape_utf8(elm.value);
                        break;
                }
            }
        }catch(e){}
    }
    for(x in obj){ ar[ar.length] = x+'='+obj[x].join(','); }
    return ar.join('&');
}
/*--- END: REQUEST PARAMETER FUNCTIONS ---*/

//IE likes to cache so we will fix it's wagon!
function _noCache(url){
    var qs = new Array();
    var arr = url.split('?');
    var scr = arr[0];
    if(arr[1]){ qs = arr[1].split('&'); }
    qs[qs.length]='noCache='+new Date().getTime();
    return scr+'?'+qs.join('&');
}
function _popWait(id){ 
    proc = document.getElementById(id);
    if( proc == null ){
        var p = document.createElement("div");
        p.id = id;
        document.body.appendChild(p);
    }
}
function _killWait(id){
    proc = document.getElementById(id);
    if( proc != null ){ document.body.removeChild(proc); }
}
function _onError(obj,inst,errCode){ 
    var msg;
    switch(errCode){
        case 1:/*parsing error*/
            msg = (inst.debug) ? obj.responseText : 'Parsing Error: The value returned could not be evaluated.';
            break;
        case 2:/*server error*/
            msg = (inst.debug) ? obj.responseText : 'There was a problem retrieving the data:\n' + obj.status+' : '+obj.statusText;
            break;
        case 3:/*browser not equiped to handle XMLHttp*/
            msg = 'Unsupported browser detected.';
            // return;/*you can remove this return to send a message to the screen*/
            break;		
    }		
    if(inst.debug){
        var debugWin = window.open('','error');
        debugWin.document.write(msg);
        debugWin.focus();
    }else{
        alert(msg);
    }
}

function _escape_utf8(data) {
    if (data=="" || data == null){ return ""; }
    data = data.toString();
    var buf = "";
    for (var i=0;i<data.length;i++) {
        var c=data.charCodeAt(i);
        var bs = [];				
        if (c>0x10000) {
            bs[0] = 0xF0 | ((c & 0x1C0000) >>> 18);
            bs[1] = 0x80 | ((c & 0x3F000) >>> 12);
            bs[2] = 0x80 | ((c & 0xFC0) >>> 6);
            bs[3] = 0x80 | (c & 0x3F);
        } else if (c>0x800) {
            bs[0] = 0xE0 | ((c & 0xF000) >>> 12);
            bs[1] = 0x80 | ((c & 0xFC0) >>> 6);
            bs[2] = 0x80 | (c & 0x3F);
        } else  if (c>0x80) {
            bs[0] = 0xC0 | ((c & 0x7C0) >>> 6);
            bs[1] = 0x80 | (c & 0x3F);
        }
        else{
            bs[0] = c;
        }
        
        if (c == 10 || c == 13){ buf += '%0'+c.toString(16); }//added to correct problem with hard returns
        else if (bs.length == 1 && c>=48 && c<127 && c!=92){buf += data.charAt(i);}
        else{ for(var j=0;j<bs.length;j++){ buf+='%'+bs[j].toString(16);} }
    }/**/
    return buf;
}
function $(id){ return document.getElementById(id); }