//*
// AJAX Object
// By Francois Dupras
// created: 2006-09-10
// last modified: 2007-01-19

// 2007-03-26
//  fixed a minor bug in the IECacheProblem using post
// 2007-03-22
//  added the async property to determine if the calls should be made async or sync (not freeze the execution or freeze it until response is reseive)
// 2007-02-09
//  BUG FIXED - BUG FOUND - call queue not working properly
//  BUG FIXED - BUG FOUND - error reporting doesn't always work
//  BUG FIXED - BUG FOUND - XML validation before callback, XML value will now be false if incorrect
// 2007-01-23
//  BUG FOUND - call queue not working properly
//  BUG FOUND - error reporting doesn't always work
//  BUG FOUND - XML validation before callback, XML value will now be false if incorrect
// 2007-01-19
//  XML validation before callback, XML value will now be false if incorrect
//  errorCallback now returns error code as second parameter
//  added getLastError method [0] error code [1] error message
// 2007-01-12
//  fix IE caching problem and none return of onreadystatechange
// 2007-01-10
//  added function for global loading and loaded (setLoadingFunction and setLoadedFunction)
//  added error callback

function ajaxObject (maxSumiltaniousConnectionParam) {
    // each XMLHttpRequest will be stored in that array
    this.ajaxRequestArray = new Array();
    
    // should the requests be async
    // set this value with setAsync(true or false)
    this.async = true;
    
    // set the maximum simultatius connection, 10 is the default one if none was provided
    this.maxSumiltaniousConnection = maxSumiltaniousConnectionParam;
    if(maxSumiltaniousConnectionParam == undefined) {
        this.maxSumiltaniousConnection = 10; // hardcoded maximum connection
    }

    // this is the method to call to replace content with response from the server
    // *url is the url of the server, if you use the GET method you can put the arguments in (page.php?arg1=value1&arg2=val2) or use the arg param
    // *id is the id of the element where you want the new content, received from the server, to be inserting (replacing existing content)
    // *method can only be "GET" or "POST"
    // *arg are the url formated argument to append to the url if GET is the method and sent as header if POST is the method selected
    // *loadingFunc is the function called as soon as the remote call is made (this function should provide your user with visual aid that the application is doing something
    //    NEW if you are using the new function setLoadingFunction this parameter becomes the parameter sent to the loading callback function
    // *loadedFunc is the function called when a response from the server was received, this function is called before the callback function
    //    NEW if you are using the new function setLoadedFunction this parameter becomes the parameter sent to the loaded callback function
    this.replace = function (url, id, method, arg, loadingFunc, loadedFunc) {
        var validate = document.getElementById(id);
        if(validate != null) {
            if(typeof(validate.innerHTML) != undefined) {
                this.request(url, '', method, arg, loadingFunc, loadedFunc, id);
            }
        }
        else {
            this.callError(1, "replace ID is not valid", true);
        }
    }
    
    // this is the method to call to make a server call
    // *url is the url of the server, if you use the GET method you can put the arguments in (page.php?arg1=value1&arg2=val2) or use the arg param
    // *callback is the function to be called with the returned information from the server
    // *method can only be "GET" or "POST"
    // *arg are the url formated argument to append to the url if GET is the method and sent as header if POST is the method selected
    // *loadingFunc is the function called as soon as the remote call is made (this function should provide your user with visual aid that the application is doing something
    //    NEW if you are using the new function setLoadingFunction this parameter becomes the parameter sent to the loading callback function
    // *loadedFunc is the function called when a response from the server was received, this function is called before the callback function
    //    NEW if you are using the new function setLoadedFunction this parameter becomes the parameter sent to the loaded callback function
    // *replaceId is only used if the callback is null, this is the id of the object.innerHTML to be relaced by the recevied responseText
    this.request = function (url, callback, method, arg, loadingFunc, loadedFunc, replaceId) {
        // validation of the inputs
        method = method.toLowerCase();
        if(url == undefined) { alert("URL function must be provided"); return false; }
        else if(typeof(callback) != "function" && replaceId == "") { alert("Callback function must be provided"); return false; }
        else if(method != "post" && method != "get") { alert("Method function must be provided and be POST or GET only"); return false; }

        // assign inner variable with the provided value        
        var innerCallback = callback;
        var innerLoadingFunc = loadingFunc;
        var innerLoadedFunc = loadedFunc;
        var innerReplaceId = replaceId;
        // get an empty or new XMLHttpRequest object
        var httpRequest = this.getHttpRequest();

        // continue if connection is clear
        if(httpRequest && (httpRequest.readyState == 4 || httpRequest.readyState == 0)) {

            // an exception can be thrown by the open and send method
            try {
                // call the loading function
                if(typeof(innerLoadingFunc) == "function") {
                    innerLoadingFunc();
                }
                else if(typeof(outterLoadingFunc) == "function") {
                    outterLoadingFunc(innerLoadingFunc);
                }
                // open and send the request
                if(method == "get") {
                    arg+="&IECacheProblem="+IECacheFix_getTime();
                    var urlComplete = url;
                    if(arg != undefined) {
                        urlComplete += '?'+arg;
                    }
                    httpRequest.open("get", urlComplete, this.async);
                    //use the internal handle function (needs to be located here because IE sucks and need it to be between open and send)
                    var readystatechange = getHandleStateChange(this)
                    httpRequest.onreadystatechange = readystatechange;
                    httpRequest.send(null);
                    // the readystatechange is never called if in sync mode, so call it when returned from the server
                    if(!this.async)
                        readystatechange();
                }
                else {
                    httpRequest.open("post", url, this.async);
					//alert("tri");
                    httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset="+this.encoding);
                    //use the internal handle function (needs to be located here because IE sucks and need it to be between open and send)
                    var readystatechange = getHandleStateChange(this)
                    httpRequest.onreadystatechange = readystatechange;
                    if(arg=="" || arg == undefined) {
                        arg = "IECacheProblem="+IECacheFix_getTime();
                    }
                    httpRequest.send(arg);
                    // the readystatechange is never called if in sync mode, so call it when returned from the server
                    if(!this.async)
                        readystatechange();
                }
            }
            catch(e) {
                // the only reson I know to get here is if you try to open a connection to a diferent host than the one you are currently on
                this.callError(2, "An exception occurred in the script. Error name: " + e.name + ". Error message: " + e.message, true);
            }
        }
        // if the connection is not clear, try again in a little while
        else {
        // create the callback function
            function getDelayedRequest(thisAjax) {
                function delayedRequest() {
                    thisAjax.request(url, (typeof(callback) == "function")?callback:'', method, arg, loadingFunc, loadedFunc, replaceId);
                }
                return delayedRequest;
            }
            setTimeout(getDelayedRequest(this), 500);
        }
        

        // generic handle function
        function getHandleStateChange(thisAjax) {
            function handleStateChange () {
                if(httpRequest.readyState == 4) {
                    // status == 200 when the connection went well
                    if(httpRequest.status == 200) {
                        if(typeof(innerCallback) == "function") {
                            // call the callback function with the return text and XML (if any)
                            xmlResponse = httpRequest.responseXML;
                            if(!xmlResponse || !xmlResponse.documentElement) {
                                //IE & Opera error or invalid xml
                                thisAjax.callError(5, 'AJAX Object reports: no XML returned or IE and Opera error', false);
                                xmlResponse = false;
                            }
                            if(xmlResponse.documentElement && xmlResponse.documentElement.nodeName == 'parseerror') {
                                //firefox error or invalid xml
                                thisAjax.callError(6, 'AJAX Object reports: no XML returned or Firefox and Opera error', false);
                                xmlResponse = false;
                            }

                            // call the loaded function, the reason why this is in the if AND in the else is that if this function 
                            // takes to much time to return and another ajax call is made with the same object a exception can occur
                            if(typeof(innerLoadedFunc) == "function") { innerLoadedFunc(); }
                            else if(typeof(outterLoadedFunc) == "function") { outterLoadedFunc(innerLoadedFunc); }


                            // first param is the true, complete response from (body, no headers) the server returned,
                            // second param is the xml returned or false if invalid xml 
                            innerCallback(httpRequest.responseText, xmlResponse);
                        }
                        else {
                            var text = httpRequest.responseText;
                            
                            // call the loaded function, the reason why this is in the if AND in the else is that if this function 
                            // takes to much time to return and another ajax call is made with the same object a exception can occur
                            if(typeof(innerLoadedFunc) == "function") { innerLoadedFunc(); }
                            else if(typeof(outterLoadedFunc) == "function") { outterLoadedFunc(innerLoadedFunc); }

                            // this code is only executed with to replace an object.innerHTML with the responseText
                            var r = document.getElementById(innerReplaceId);
                            if(r) {
                                r.innerHTML = text;
                            }
                        }
                    }
                    else {
                        // status is not 200, display the status to the user (404: not found, 403: acces denied, 500: internal error)
                        // TODO: setup a callback function to handle this event
                        thisAjax.callError(3, "Error connecting; Sever status: "+httpRequest.status+"\n"+httpRequest.responseText, true);
                    }
                }
            }
            return handleStateChange;
        }


        return true;
    }
    
    // get a free or newly created XMLHttpRequest object
    this.getHttpRequest = function () {
        var httpRequest = undefined;
        // loop trought the already created object to find one free
        for(i=0; i<this.ajaxRequestArray.length; i++) {
            // if one was found assign it and break the loop
            if(this.ajaxRequestArray[i].readyState == 4 || this.ajaxRequestArray[i].readyState == 0) {
                httpRequest = this.ajaxRequestArray[i];
                break;
            }
        }
        // if no free object was found and that the max simultatious connection is not reach create a new one
        if(httpRequest == undefined && this.ajaxRequestArray.length < this.maxSumiltaniousConnection) {
            // try to create a new object for the mordern browser
            try {
				//alert("tri");
          			var browser = navigator.appName;
				    if(browser == "Microsoft Internet Explorer")
					{
						httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
					}
					else
					{
                		httpRequest = new XMLHttpRequest();
					}
            } catch(e) {
                // try to create object for older browser
                var xmlHttpVersion = new Array("Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP");
                for(var i = 0; i < xmlHttpVersion.length && !httpRequest; i++) {
                    try {
                        httpRequest = new ActiveXObject(xmlHttpVersion[i]);
                    } catch(e) { }
                }
            }
            // get the next index for keeping the object
            var nextIndex = this.ajaxRequestArray.length;
            // assign the new object to the list
            this.ajaxRequestArray[nextIndex] = httpRequest;
        }
        // return the free object, the new object or false if the max is reach and all are busy
        return httpRequest;
    }

    // these variables are used to store the callbacks function that will fire on starting a call, and end when the response is received.
    var outterLoadingFunc = '';
    var outterLoadedFunc = '';
    // call setLoadingFunction and setLoadedFunction to set the function to be called when a starting a call to the server (request or replace) and when the response is received.
    // the callback function should take 1 parameter each, this parameter will be what you pass in the request or replace call.
    this.setLoadingFunction = function (callback) {
        outterLoadingFunc = callback;
    }
    this.setLoadedFunction = function (callback) {
        outterLoadedFunc = callback;
    }
    
    // set the function that will be called if an error occurs
    var errorCallback = '';
    var lastError = new Array(0,'');
    this.setErrorCallback = function (callback) {
        errorCallback = callback;
    }
    // private function to handle error
    this.callError = function (errCode, errMsg, show) {
        lastError = new Array(errCode, errMsg);
        if(typeof(errorCallback) == "function") {
            errorCallback(errMsg, errCode);
        }
        else if(show == true) {
            alert(errMsg);
        }
    }
    
    // return an array with [0] last error code and [1] last error message
    this.getLastError = function () {
        return lastError;
    }

    // text-encoding for the transmision with the server, user function setEncoding to set it
    this.encoding = 'iso-8859-15';
    // text-encoding for the transmision with the server, user function setEncoding to set it
    this.setEncoding = function(newCharset) {
        var old = this.encoding;
        this.encoding = newCharset;
        return old;
    }
    
    this.setAsync = function(newValue) {
        this.async = newValue;
    }
    
    // transform the form elements into url encoded arguments (name=value&name2=value2)
    this.formToUrl = function(formId) { return this.formToUrlArguments(formId); }
    this.formToUrlArgument = function(formId) { return this.formToUrlArguments(formId); }
    this.formToUrlArguments = function(formId) {
        var form = false;
        // you can pass ether the object or the id of the form
        if(typeof(formId) == "object") {
            form = formId;
        }
        else {
            form=document.getElementById(formId);
        }
        
        // if form is an object
        if(form != null && typeof(form) == "object" && form.tagName.toLowerCase() == "form") {
            // r is the return string... yes, I know, this name could be more descriptive.
            var r = "";
            // loop trought all of the form elements
            for(var i = 0; i < form.elements.length; i++) {
                var formElement = form.elements[i];
                // will not include disabled or unamed element (like a submit button <input type="submit" value="Send"/>)
                if(formElement.name != "" && formElement.disabled == false) {
                    // text, hidden, password, textarea and single select (dropdown) are all passed the same way, basic text
                    if(formElement.type == "text" || formElement.type == "hidden" || formElement.type == "password" || formElement.type == "textarea" || formElement.type == "select-one") {
                        r+= encodeURIComponent(formElement.name)+"="+encodeURIComponent(formElement.value)+"&";
                    }
                    // checkbox are send but the value is only sent if the checkbox is checked
                    else if (formElement.type == "checkbox") {
                        r+= encodeURIComponent(formElement.name)+"=";
                        if (formElement.checked == true) {
                            r+= encodeURIComponent(formElement.value)+"&";
                        }
                        r+= "&"
                    }
                    // only the selected radio button and value is send
                    else if (formElement.type == "radio" && formElement.checked) {
                        r+= encodeURIComponent(formElement.name)+"=";
                        r+= encodeURIComponent(formElement.value)+"&";
                    }
                    // for a case like the multiple select (listbox) an array of value is send
                    else if(formElement.type == "select-multiple") {
                        for(var i = 0;i < formElement.length;i++){
                            if(formElement.options[i].selected == true){
                                //TODO: this will work with PHP but as not been tested with ASP
                                r+= encodeURIComponent(formElement.name)+"[]="+encodeURIComponent(formElement.options[i].value)+"&";
                            }
                        }
                    }
                    //TODO: add the file, if possible.
                    else if(formElement.type == "file") {
                        //alert("DOM Input File: "+formElement.value);
                    }
                }
            }
            return r;
        }
        else {
            this.callError(4, "The argument passed to formToUrlArguments function is not a form ID or a form object", true);
        }
        return false;
    }
    
    return this;
}

function IECacheFix_getTime() {
    var d = new Date();
    return d.getTime();
}
/**/
