/**
    @fileoverview
    This JavaScript file represents the core browser-side functionality
    supplied by Taconite. In general, the tools in this file wrap an instance
    of XMLHttpRequest object and provide utility methods for gather data from
    form elements to be sent to the server as par of an Ajax request.
*/

/**
    Constructor for the AjaxRequest class. 

    <br><br>
    Example:

    <br><br>
    var ajaxRequest = new AjaxRequest("YOUR_URL");

    @class The AjaxRequest object wraps an instance of XMLHttpRequest and provides 
    facilities for setting functions that are called before a request is made
    and after a request returns. By default, AjaxRequest handles the server
    response by simply calling eval(), passing to it the responseText from 
    the XMLHttpRequestObject, of course assuming that the response was 
    generated by Taconite on the server side and that running eval() will 
    update the web page.<br><br>Example Usage:<br><br>var ajaxRequest = new AjaxRequest("YOUR_URL");
    <br>ajaxRequest.addFormElements("form_element_id_attribute_value");
    <br>ajaxRequest.sendRequest();

    @constructor
    @param {String} a String repesenting the URL to which the Ajax request
    will be sent.
*/
/*
====================================================================================================================
#	Date			Developer	Brief Description of Change and Reason for Change
====================================================================================================================
1.	29-Mar-2007		ph			(Removed. Because for development only)
								Added debug iFrame for debug purpose. The returned text requested by ajax will be
								displayed in the iFrame.
2.	14-May-2007		ph			Enable user to show the loading bar at a specified place.
								Sample usage: 
								var ajaxRequest = new AjaxRequest("my_url.cfm");
								// Container ID to append the loading bar. If not defined, wont show loading bar.
								ajaxRequest.setLoadingContainer("my_content");
								// This is optional to set Id for the loading bar
								ajaxRequest.setLoadingBarId("my_loading_bar");
								ajaxRequest.sendRequest();
3.	05-Nov-2007		ph			Set "Loading..." word color to black.
4.	06-May-2008		waihoong	Set the method to "POST" instead of "GET"
5.	25-Jun-2008		ph			Added runShowLoadingBar to run showLoadingBar.
								Added hasRunShowLoadingBar to indicate whether showLoadingBar has run.
6.	02-Jul-2008		ph			Changed encodeURIComponent to escape function, the formats are different, we want 
								the format generated by escape function.
7.	04-Aug-2009		ph			Validate the XML first, if invalid XML throw an error.
								Added an attribute whether the loading container needs to be reserved, otherwise
								will be empty.
8.	23-Sep-2009		ph			Escape select input value (convert to URL encoded format).
===================================================================================================================
*/
var taconite_client_version=1.6;
var ajax_script_root = "/ajaxparser"; // #2 Root of this script
function AjaxRequest(url,method) {
    /** @private */
    var self = this;

    /** @private */
    var xmlHttp = createXMLHttpRequest();
    
    /** @private */
    var queryString = "";

    /** @private */
    var requestURL = url;

    /** @private */ /* #4 */
	if(method !== undefined && method == 1)
	{
    	var method = "POST";
	}
	else
	{
		var method = "GET";
	}

    /** @private */
    var preRequest = null;

    /** @private */
    var postRequest = null;

    /** @private */
    var debugResponse = false;
	
    /** @private */
    var async = true;

    /** @private errorHandler*/ 
    var errorHandler = null;
	
	/* #2 Variables for loading bar */
	var loading_container = "";
	var loading_bar_id = "";
	var loading_auto_remove = true;
	/* end #2 Variables for loading bar */
	var reserve_loading_container = false; // #7 Reserve loading container? If not, will be empty.
	
	var script_root = ajax_script_root; // #2 Added the directory where this script exists
	
	var hasRunShowLoadingBar = false; // #5


    /**
        Return the instance of the XMLHttpRequest object wrapped by this object.
        @return XMLHttpRequest
    */
    this.getXMLHttpRequestObject = function() {
        return xmlHttp;
    }

    /**
        Set the pre-request function. This function will be called prior to 
        sending the Ajax request. The pre-request function is passed a reference
        to this object.
        @param {Function} The function to be called prior to sending the Ajax
        request. The function is passed a refernce of this object.
    */
    this.setPreRequest = function(func) {
        preRequest = func;
    }

    /**
        Set the post-request function. This function will be called after the
        response has been received and after eval() has been called using the 
        XMLHttpRequest object's responseText. The post-request function is passed 
        a reference to this object.
        @param {Function} The function to be called after receiving the Ajax
        response. The function is passed a refernce of this object.
    */
    this.setPostRequest = function(func) {
        postRequest = func;
    }
    
    /**
        Return the post request function.
    */
    this.getPostRequest = function() {
        return postRequest;
    }

    /**
        Send the Ajax request using the POST method. Use with caution -- some
        browsers do not support the POST method with the XMLHttpRequest object.
    */
    this.setUsePOST = function() {
        method = "POST";
    }

    /**
        Send the Ajax request using the GET method, where parameters are sent
        as a query string appended to the URL. This is the default behavior.
    */
    this.setUseGET = function() {
        method = "GET";
    }

    /**
        Enable client-side debugging.  The server's response will be written
        to a text area appended to the bottom of the page.  If parsing is
        performed on the client side, then the results of the parsing operations
        are shown in their own text areas.
    */
    this.setEchoDebugInfo = function() {
        debugResponse = true;
    }

    /**
        Indicate if debugging is enabled.
        @return boolean
    */
    this.isEchoDebugInfo = function() {
        return debugResponse;
    }

    /**
        Set the query string that will be sent to the server. For GET
        requests, the query string is appended to the URL. For POST
        requests, the query string is sent in the request body. This 
        method is useful, for example, if you want to send an XML string
        or JSON string to the server.
        @param {String} qa, the new query string value.
    */
    this.setQueryString = function(qs) {
        queryString = qs;
    }

    /**
        Return the query string.
        @return The query string.
    */
    this.getQueryString = function() {
        return queryString;
    }

    /** 
        @param {Boolean} asyncBoolean, set to true if asynchronous request, false synchronous request. 
    */
    this.setAsync = function(asyncBoolean){
            async = asyncBoolean;
    }

    /** 
        @param {Function} Set the error handler function that is called if the 
        server's HTTP response code is something other than 200.
    */	
    this.setErrorHandler = function(func){
        errorHandler = func;
    }
	
	/* #2 Function for user to set the loading bar variables */
	this.setLoadingContainer = function(container_id) {
        loading_container = container_id;
    }
	this.setLoadingBarId = function(lid) {
        loading_bar_id = lid;
    }
	this.getLoadingBarId = function() {
        return loading_bar_id;
    }
	this.setLoadingAutoRemove = function(option) {
        loading_auto_remove = option;
    }
	this.getLoadingAutoRemove = function() {
        return loading_auto_remove;
    }
	/* end #2 Function for user to set the loading bar variables */
	/* #7 Function to set reserve_loading_container */
	this.reserveLoadingContainer = function(option){
		reserve_loading_container = option;
	}
	/* end #7 */
	
    /**
        Add all of the form elements under the specified form to the query
        string to be sent to the server as part of the Ajax request. The values
        are automatically encoded.
        @param {String} formID, the value of the id attribute of the form from
        which you wish to accumulate the form values.
    */
    this.addFormElements = function(formID) {
        var formElements = document.getElementById(formID).elements;
        var values = toQueryString(formElements);
        accumulateQueryString(values);
    }
	
	/* #5 Function for manually run the loadinig bar. */
	this.runShowLoadingBar = function(){
		showLoadingBar (self);
	};
	/* end #5 */

    /** @private */
    function accumulateQueryString(newValues) {
        if(queryString == "") {
            queryString = newValues; 
        }
        else {
            queryString = queryString + "&" +  newValues;
        }
    }

    /**
        Add the name/value pair to the query string.
        @param {String} name
        @param {String} value
    */
    this.addNameValuePair = function(name, value) {
        var nameValuePair = name + "=" + escape(value); // #6
        accumulateQueryString(nameValuePair);
    }

    

   /**
        Same as addNamedFormElements, except it will filter form elements by form's id.
        For example, these are all valid uses:<br>
        <br>ajaxRequest.addNamedFormElements("form-id""element-name-1");
        <br>ajaxRequest.addNamedFormElements("form-id","element-name-1",
        "element-name-2", "element-name-3");
    */
    this.addNamedFormElementsByFormID = function() {
        var elementName = "";
        var namedElements = null;

        for(var i = 1; i < arguments.length; i++) {
            elementName = arguments[i];
            namedElements = document.getElementsByName(elementName);
            var arNamedElements = new Array();
            for(j = 0; j < namedElements.length; j++) {
                if(namedElements[j].form  && namedElements[j].form.getAttribute("id") == arguments[0]){
                    arNamedElements.push(namedElements[j]);				
                }
            }
            if(arNamedElements.length > 0){
                elementValues = toQueryString(arNamedElements);
	        accumulateQueryString(elementValues);
            }
        }
    }








    /**
        Add the values of the named form elements to the query string to be
        sent to the server as part of the Ajax request. This method takes any 
        number of Strings representing the form elements for wish you wish to 
        accumulate the values. The Strings must be the value of the element's 
        name attribute.<br><br>For example, these are all valid uses:<br>
        <br>ajaxRequest.addNamedFormElements("element-name-1");
        <br>ajaxRequest.addNamedFormElements("element-name-1", "element-name-2", "element-name-3");
    */
    this.addNamedFormElements = function() {
        var elementName = "";
        var namedElements = null;

        for(var i = 0; i < arguments.length; i++) {
            elementName = arguments[i];
            namedElements = document.getElementsByName(elementName);

            elementValues = toQueryString(namedElements);

            accumulateQueryString(elementValues);
        }

    }

    /**
        Add the values of the id'd form elements to the query string to be
        sent to the server as part of the Ajax request. This method takes any 
        number of Strings representing the ids of the form elements for wish you wish to 
        accumulate the values. The Strings must be the value of the element's 
        name attribute.<br><br>For example, these are all valid uses:<br>
        <br>ajaxRequest.addFormElementsById("element-id-1");
        <br>ajaxRequest.addFormElementsById("element-id-1", "element-id-2", "element-id-3");
    */
    this.addFormElementsById = function() {
        var id = "";
        var element = null;
        var elements = new Array();

        for(var h = 0; h < arguments.length; h++) {
            element = document.getElementById(arguments[h]);
            if(element != null) {
                elements[h] = element;
            }
        }

        elementValues = toQueryString(elements);
        accumulateQueryString(elementValues);
    }

    /**
        Send the Ajax request.
    */
    this.sendRequest = function() {
        if(preRequest) {
            preRequest(self);
        }

        var obj = this;
		if(async)
	        xmlHttp.onreadystatechange = function () { handleStateChange(self) };

        if(requestURL.indexOf("?") > 0) {
            requestURL = requestURL + "&ts=" + new Date().getTime();
        }
        else {
            requestURL = requestURL + "?ts=" + new Date().getTime();
        }
		
		if( !hasRunShowLoadingBar ) // #5
			showLoadingBar (self); // #2 Show the loading bar

        try {
            if(method == "GET") {
                if(queryString.length > 0) {
                    requestURL = requestURL + "&" + queryString;
                }
                xmlHttp.open(method, requestURL, async);
                xmlHttp.send(null);
            }
            else {
                xmlHttp.open(method, requestURL, async);
                //Fix a bug in Firefox when posting
                try {
                    if (xmlHttp.overrideMimeType) {
                        xmlHttp.setRequestHeader("Connection", "close");//set header after open
                    }			
                }
                catch(e) {
                    // Do nothing
                }
                xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 
                xmlHttp.send(queryString);
            }
        }
        catch(exception) {
            // #2 Call to remove the loading bar 1st
			// #7 Commented. removeLoadingBar();
			// #7
			showErrorOnLoadingBar(ajaxRequest);
			if(errorHandler) {
                errorHandler(self, exception);
            }
            else {
                throw exception;
            }
        }

        if(!async) {  //synchronous request, handle the state change
            handleStateChange(self);
        }

        if(self.isEchoDebugInfo()) {
            echoRequestParams();
        }
    }

    handleStateChange = function(ajaxRequest) {
        if(ajaxRequest.getXMLHttpRequestObject().readyState != 4) {
            return;
        }		
        try {

			var debug = ajaxRequest.isEchoDebugInfo();
            if(debug) {
                echoResponse(ajaxRequest);
            }
			
			// #7 Throw error if invalid XML
			if( !validateXML(ajaxRequest.getXMLHttpRequestObject().responseText) ){
				throw "invalid_xml";
			}

            //handle null responseXML
            var nodes = null;
            if (ajaxRequest.getXMLHttpRequestObject().responseXML != null) {
				nodes = ajaxRequest.getXMLHttpRequestObject().responseXML.documentElement.childNodes;
            }
            else {
                nodes = new Array();
            }

            var parser = new XhtmlToDOMParser();
            var parseInBrowser = "";
            for(var i = 0; i < nodes.length; i++) {
                if(nodes[i].nodeType != 1 || !isTaconiteTag(nodes[i])) {
                    continue;
                }

                parseInBrowser = nodes[i].getAttribute("parseInBrowser");
                if(parseInBrowser == "true") {
                    parser.parseXhtml(nodes[i]);
                    var js = parser.getJavaScript();
                    if(debug) {
                        echoParsedJavaScript(js);
                    }
                }
                else {
                    eval(nodes[i].firstChild.nodeValue);
                }
            }

            if(ajaxRequest.getPostRequest()) {
                var f = ajaxRequest.getPostRequest();
                f(ajaxRequest);
            }
        }
        catch(exception) {
            // #7 
			showErrorOnLoadingBar(ajaxRequest);
			if(errorHandler) {
                errorHandler(self, exception);
            }
            else {
                throw exception;
            }
        }
		// #2 Remove loading bar after getting the response and updating the page
		if( ajaxRequest.getLoadingAutoRemove() )
			removeLoadingBar(ajaxRequest);
    }

	
	// #2 Function to create and place the loading bar
	function showLoadingBar (ajaxRequest) {

		if( loading_container != "" ){
			var dcontainer = document.getElementById(loading_container);
			if( dcontainer ){
			
				// #7 Empty loading container first if needed.
				if( !reserve_loading_container ){
					emptyElement(dcontainer);
				}
				// end #7 Empty loading container first if needed.
				
				// #2 Create and place the loading bar				
				var loading_bar = document.createElement("div");
				
				// Assign id and store id
				if( ajaxRequest.getLoadingBarId() != "" ) {
					loading_bar.id = ajaxRequest.getLoadingBarId();
				}
				else {
					for( var i=0; ; i++ ){
						var check_loading_bar = document.getElementById("ajax_loading_bar"+i);
						if( !check_loading_bar ){
							loading_bar.id = "ajax_loading_bar"+i;
							ajaxRequest.setLoadingBarId(loading_bar.id);
							break;
						}					
					}					
				}
							
				loading_bar.style.font = "bold 12 Arial, Helvetica, sans-serif";
				loading_bar.style.color = "#000000"; // #3
				loading_bar.style.background = "#FFFFFF";
				loading_bar.style.padding = "5px 5px 5px 5px";
				loading_bar.style.width = "85px";
				
				var loading_content = document.createElement("img");
				loading_content.src = script_root + "/loading_circle.gif";
				loading_bar.appendChild(loading_content);
				
				var loading_content = document.createTextNode(" Loading...");
				loading_bar.appendChild(loading_content);
				
				var center_tag = document.createElement("center");
				center_tag.appendChild(loading_bar);
				
				dcontainer.appendChild(center_tag);
	
			}
			hasRunShowLoadingBar = true; // #5
		}
	
	}
	
	// #2 Function to remove the loading bar
	function removeLoadingBar(ajaxRequest) {		
		if( ajaxRequest.getLoadingBarId() != "" ){
			var loading_bar = document.getElementById(ajaxRequest.getLoadingBarId());
			if( loading_bar ){
				// Remove the center tag rather than the loading bar itself
				loading_bar.parentNode.parentNode.removeChild(loading_bar.parentNode);
			}
		}
	}
	
	// #7 Function to show error message on loading bar
	function showErrorOnLoadingBar(ajaxRequest){
		if( loading_container != "" ){
			if( !reserve_loading_container ){
				emptyElement(document.getElementById(loading_container));
			}
			else{
				removeLoadingBar(ajaxRequest);
			}
			var dcontainer = document.getElementById(loading_container);
			// Create and place the loading bar				
			var loading_bar = document.createElement("div");
			
			// Assign id and store id
			if( ajaxRequest.getLoadingBarId() != "" ) {
				loading_bar.id = ajaxRequest.getLoadingBarId();
			}
			else {
				for( var i=0; ; i++ ){
					var check_loading_bar = document.getElementById("ajax_loading_bar"+i);
					if( !check_loading_bar ){
						loading_bar.id = "ajax_loading_bar"+i;
						ajaxRequest.setLoadingBarId(loading_bar.id);
						break;
					}					
				}					
			}
						
			loading_bar.style.font = "bold 12 Arial, Helvetica, sans-serif";
			loading_bar.style.color = "red"; // #3
			loading_bar.style.background = "#FFFFFF";
			loading_bar.style.padding = "5px 5px 5px 5px";
			loading_bar.style.width = "125px";
			
			var loading_content = document.createTextNode("Connection failed.");
			loading_bar.appendChild(loading_content);
			
			var center_tag = document.createElement("center");
			center_tag.appendChild(loading_bar);
			
			dcontainer.appendChild(center_tag);
		}
	}
	// end #7 Function to show error message on loading bar
	
	// #7 Function to clear container
	function emptyElement(ele){
		while(ele.childNodes.length >0){
			ele.removeChild(ele.childNodes[0]);
		}
	}
	// end #7 Function to clear container
	
	
    /** @private */
    function isTaconiteTag(node) {
        return node.tagName.substring(0, 9) == "taconite-";
    }

    /** @private */
    function toQueryString(elements) {
        var node = null;
        var qs = "";
        var name = "";

        var tempString = "";
        for(var i = 0; i < elements.length; i++) {
            tempString = "";
            node = elements[i];
            name = node.getAttribute("name");

            //use id if name is null
            if (!name) {
            	name = node.getAttribute("id");
            }

            if(node.tagName.toLowerCase() == "input") {
                if(node.type.toLowerCase() == "radio" || node.type.toLowerCase() == "checkbox") {
                    if(node.checked) {
                        tempString = name + "=" + node.value;
                    }
                }

                if(node.type.toLowerCase() == "text" || node.type.toLowerCase() == "hidden" || node.type.toLowerCase() == "password") {
                    tempString = name + "=" + escape(node.value); // #6
                }
            }
            else if(node.tagName.toLowerCase() == "select") {
                tempString = getSelectedOptions(node);
            }

            else if(node.tagName.toLowerCase() == "textarea") {
                tempString = name + "=" + escape(node.value); // #6
            }

            if(tempString != "") {
                if(qs == "") {
                    qs = tempString;
                }
                else {
                    qs = qs + "&" + tempString;
                }
            }

        }

        return qs;

    }

    /** @private */
    function getSelectedOptions(select) {
        var options = select.options;
        var option = null;
        var qs = "";
        var tempString = "";

        for(var x = 0; x < options.length; x++) {
            tempString = "";
            option = options[x];

            if(option.selected) {
                tempString = select.name + "=" + escape(option.value); // #8 Escape string
            }

            if(tempString != "") {
                if(qs == "") {
                    qs = tempString;
                }
                else {
                    qs = qs + "&" + tempString;
                }
            }
        }

        return qs;
    }

    /** @private */
    function echoResponse(ajaxRequest) {
        var echoTextArea = document.getElementById("debugResponse");
        if(echoTextArea == null) {
            echoTextArea = createDebugTextArea("Server Response:", "debugResponse");
        }
        var debugText = ajaxRequest.getXMLHttpRequestObject().status 
            + " " + ajaxRequest.getXMLHttpRequestObject().statusText + "\n\n\n";
        echoTextArea.value = debugText + ajaxRequest.getXMLHttpRequestObject().responseText;
    }

    /** @private */
    function echoParsedJavaScript(js) {
        var echoTextArea = document.getElementById("debugParsedJavaScript");
        if(echoTextArea == null) {
            var echoTextArea = createDebugTextArea("Parsed JavaScript (by JavaScript Parser):", "debugParsedJavaScript");
        }
        echoTextArea.value = js;
    }

    /** @private */
    function createDebugTextArea(label, id) {
        echoTextArea = document.createElement("textarea");
        echoTextArea.setAttribute("id", id);
        echoTextArea.setAttribute("rows", "15");
        echoTextArea.setAttribute("style", "width:100%");
        echoTextArea.style.cssText = "width:100%";

        document.getElementsByTagName("body")[0].appendChild(document.createTextNode(label));
        document.getElementsByTagName("body")[0].appendChild(echoTextArea);
        return echoTextArea;
    }


    /** @private */
    function echoRequestParams() {
        var qsTextBox = document.getElementById("qsTextBox");
        if(qsTextBox == null) {
            qsTextBox = createDebugTextBox("Query String:", "qsTextBox");
        }
        qsTextBox.value = queryString;

        var urlTextBox = document.getElementById("urlTextBox");
        if(urlTextBox == null) {
            urlTextBox = createDebugTextBox("URL (Includes query string if GET request):", "urlTextBox");
        }
        urlTextBox.value = requestURL;
    }

    /** @private */
    function createDebugTextBox(label, id) {
        textBox = document.createElement("input");
        textBox.setAttribute("type", "text");
        textBox.setAttribute("id", id);
        textBox.setAttribute("style", "width:100%");
        textBox.style.cssText = "width:100%";

        document.getElementsByTagName("body")[0].appendChild(document.createTextNode(label));
        document.getElementsByTagName("body")[0].appendChild(textBox);
        return textBox;
    }


}

/**
    Create an instance of the XMLHttpRequest object, using the appropriate
    method for the type of browser in which this script is running. For Internet
    Explorer, it's an ActiveX object, for all others it's a native JavaScript
    object.
    @return an instance of the XMLHttpRequest object.
*/
function createXMLHttpRequest() {
    var req = false;
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
    }
    else if (window.ActiveXObject) {
       	try {
            req = new ActiveXObject("Msxml2.XMLHTTP");
      	}
        catch(e) {
            try {
                req = new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch(e) {
                req = false;
            }
        }
    }
    return req;
}

// #7
function validateXML(string){
	// code for Mozilla, Firefox, Opera, etc.
	if (document.implementation.createDocument){
		var parser=new DOMParser();
		var xmlDoc=parser.parseFromString(string,"text/xml");
	
		if (xmlDoc.documentElement.nodeName=="parsererror"){
			return false;
		}
		else{
			return true;
		}
	}
	else{
		/* Either IE or browser can't handle xml, ignore it.
		No check for IE, as the IE XML validator can't check for DTD,
		and IE will throw error anyway when parsing if invalid XML. */
		return true;
	}
}
// end #7
