/********************
DESCRIPTION
This object is designed to retrieve data from the server dynamically without the use of frames.

REQUIREMENTS
javascript_extensions.js

DIRECTIONS
New up an AJAX object with the required path and processor parameters described below. 
The function designated as the processor will be run when the data has completed loading,
and is passed the ajax object. You can then access the properties described below.  With
synchronous communication (the default), it is possible to simply access the .text and .xml
data without having to use a processor function as the rest of the script will wait until
the AJAX operations have completed.

Remember, when parsing XML, text inside an element is considered a node and you return the text
inside the text node by referring to the nodeValue property.
For example <menu>item 1</menu> is referred to by
[xml].getElementsByTagName("menu").childNodes[0].nodeValue.

Also note that when Firefox returns each chunk of white space as a text node.
For example:
<menu>
	item 1
</menu>
is obtained via [xml].getElementsByTagName("menu").childNodes[1].nodeValue in Firefox
whereas <menu>item 1</menu>
is obtained via [xml].getElementsByTagName("menu").childNodes[0].nodeValue in Firefox
IE ignores white space and always uses childNodes[0] for the internal text node.

PARAMETERS
path			the path to the file that will return the data (e.g. a php or xml file)
processor		OPTIONAL - a pointer to a function that will process the data when it is loaded
				This is the only way to handle data after the completion of the call when using asynchronous communication
script			OPTIONAL - if you want the object to store the result of a function on
				the page specified in the path instead of processing the entire page,
				you may specify a string that represents the function call and parameters
				NOTE: when the function is to return an xml value, you must set the
				"return type" parameter to xml
asynchronous	OPTIONAL - [false | true] if you want asynchronous communication, pass a boolean false
				Processor becomes a required param when asynchronous communication is set to true
base path		OPTIONAL - This is the default path to get to the ajax_api_processor.php file. This will need
				to be altered if calling from a place other than the same directory as the calling page.
method			OPTIONAL - [get | post]
form			OPTIONAL - a form object to be submitted to the path or the action of the form if no path is specified
form id			OPTIONAL - same as form, except you pass the id of the form instead of the object
data			OPTIONAL - a string of data to be submitted to the path
				FORMAT: var1Name=data1&var2Name=data2&...
return type		OPTIONAL - when calling a function that returns xml data, this value must be set to xml

PROPERTIES
xml				the data store for any xml data returned - this can be parsed using built-in methods
				like .childNodes and .nodeValue (to retrieve data in a given element)
text			the data store for any text data returned
trigger			a pointer to the object that newed up the AJAX object

EXAMPLE - SYNCHRONOUS COMMUNICATION
function body_onload()
{
	var ajax = new AJAX({ path: "my_data.php?Record=13" });
	alert( ajax.text ); // displays any returned text
	alert( ajax.xml ); // displays [object] if there is any xml returned as this is actually an xml object
	alert( ajax.trigger ); // this will return null as it was called by the onload of the body in this example
}

EXAMPLE - ASYNCHRONOUS COMMUNICATION
function body_onload()
{
	new AJAX({ path: "my_data.php?Record=13", processor: process_data });
}
function process_data( ajax )
{
	alert( ajax.text ); // displays any returned text
	alert( ajax.xml ); // displays [object] if there is any xml returned as this is actually an xml object
	alert( ajax.trigger ); // this will return null as it was called by the onload of the body in this example
}

EXAMPLE - CALLING DATA VIA A USER DRIVEN EVENT
function process_data( ajax )
{
	// inserts a space separated list of the text in each item tag within the first menu tag
	var div = document.getElementById( ajax.trigger.value );
	var items = ajax.xml.getElementsByTagName("menu")[0].getElementsByTagName("item");
	for ( var i = 0; i < items.length; i++ )
		div.innerHTML += items[i].childNodes[0].nodeValue + " ";
}
<div id="div1"></div>
<div id="div2"></div>
<select id="My_Select" onchange="new AJAX({ path: 'my_data.php', processor: process_data });">
	<option value="" selected>select an option</option>
	<option value="div1">option 1</option>
	<option value="div2">option 2</option>
</select>

EXAMPLE - PHP GENERATED XML DATA
<?php // note that you must always include the header declaration to ensure delivery of the data in an XML format
header("Content-Type: text/xml");
echo "<data>$display</data>";
?>

function process_data( ajax )
{
	// inserts a space separated list of the text in each item tag within the first menu tag
	// displays 1 if option 1 was selected and 2 if option 2 was selected
	alert( ajax.xml.getElementsByTagName("data")[0].childNodes[0] );
}
<select id="My_Select" onchange="if ( this.value != '' ) new AJAX({ path: 'my_data.php?display='+this.value, processor: process_data });">
	<option value="" selected>select an option</option>
	<option value="1">option 1</option>
	<option value="2">option 2</option>
</select>

EXMAPLE - RETRIEVING TEXT
<?php
echo "$display";
?>

function process_data( ajax )
{
	// inserts a space separated list of the text in each item tag within the first menu tag
	// displays 1 if option 1 was selected and 2 if option 2 was selected
	alert( ajax.text );
}
<select id="My_Select" onchange="if ( this.value != '' ) new AJAX({ path: 'my_data.php?display='+this.value, processor: process_data });">
	<option value="" selected>select an option</option>
	<option value="1">option 1</option>
	<option value="2">option 2</option>
</select>

EXAMPLE - PROCESSING A FUNCTION INSTEAD OF A PAGE
// instead of returning the result of the whole my_apis.php page, it instead runs the get_id function in my_apis.php with a parameter
// of kevin, and returns the results of the function call (i.e. ajax.xml and ajax.text will be based on the return value of the function
// instead of what's on the my_apis.php page)
new AJAX({ path: "my_apis.php", processor: my_processor_function, script: "get_id('kevin')" });

EXAMPLE - POSTING A FORM
new AJAX({ "form id" : "My_Form" }); // all other settings are handled automatically and the form action is used as the path
********************/

AJAX.iframe_base_name = "AJAX_IFRAME_";
AJAX.num_iframes = 0;

function AJAX( params )
{
	if ( params.script )
		this.script = encodeURIComponent( params.script );
	this.base_path = params["base path"] || "../javascript_libraries/";
	this.path = params.path;
	this.processor = params.processor;
	this.method = params.method || "post";
	this.data = params.data || "";
	this.asynchronous = params.asynchronous || false;
	this.return_type = params["return type"] || "";
	this.form = params.form;
	if ( params["form id"] )
		this.form = document.getElementById( params["form id"] );
	this.large_post = params.large_post || false;
	
	// if this is a form, checking for file elements
	// if found, normal AJAX functionality will be terminated
	// and a faux iframe solution will be implemented
	var file_upload = false;
	if ( this.form )
	{
		for ( var i = 0; i < this.form.elements.length; i++ )
		{
			var element = this.form.elements[i];
			if ( element.tagName.toLowerCase() == "input" && element.type == "file" )
			{
				file_upload = true;
				break;
			}
		}
	}
	
	if ( file_upload || this.large_post )
	{
		this.iframe_name = AJAX.iframe_base_name + AJAX.num_iframes;
		this.iframe = document.createElement("<iframe name='" + this.iframe_name + "'>");
		AJAX.num_iframes++;
		
		this.iframe.style.display = "none";
		
		document.body.appendChild( this.iframe );
		this.iframe.attachEvent( "onload", this.iframe_process_complete.bind(this) );
		
		this.form.target = this.iframe_name;
		
		if ( file_upload )
		 	this.form.encoding = "multipart/form-data";
		
		this.form.submit();
		
		// stopping additional processing as it is no longer possible to use true AJAX because of the presence of a file element
		return;
	}
	else if ( this.form )
	{
		this.method = "post";
		this.serialize_form(); // converts the form element data to this.data
		if ( !this.path )
			this.path = this.form.action;
	}
	if ( this.data )
		this.method = "post";
	
	this.text = null;
	this.xml = null;
	
	// if the AJAX obj is to process a script in the path file instead of running the path itself...
	if ( this.script )
	{
		// ensuring the script has a trailing ;
		if ( this.script.charAt(this.script.length - 1) != ";" )
			this.script += ";";
		this.path = this.base_path + "ajax_api_processor.php?API_URL=" + escape(this.path) + "&Script=" + this.script + ( this.return_type == "xml" ? "&return_type=xml" : "" );
	}
	
	var e = null;
	this.trigger = null;
	if ( typeof event != "undefined" ) // IE
		e = event;
	else if ( typeof params.e != "undefined" ) // Firefox
		e = params.e;
	if ( e && typeof e.target != "undefined" ) // Firefox
		this.trigger = e.target;
	else if ( e && e.srcElement ) // IE
		this.trigger = e.srcElement;
	
	this.obj = this.get_obj();
	this.start_process();
}
AJAX.prototype.iframe_process_complete = function()
{
	this.text = window.frames[ this.iframe_name ].document.body.innerHTML;
	
	this.iframe.removeNode(true);
	
	if ( this.processor )
		this.processor();
}
AJAX.prototype.start_process = function()
{
	if ( this.obj )
	{
		// if the call is asynchronous, the data will not be processed until it is ready
		if ( this.asynchronous )
			this.obj.onreadystatechange = this.process_data.bind(this);
		
		// try is used to catch cross domain calls
		try { this.obj.open( this.method, this.path, this.asynchronous ); }
		catch (e) { return; }
		
		if ( this.method == "post" )
			this.obj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		this.obj.send( this.data );
		
		// if the call is synchronous, the data will be processed now
		// this is required due to Firefox's inability to process the readystate in a synchronous transaction
		if ( !this.asynchronous )
		{
			this.xml = this.obj.responseXML;
			this.text = this.obj.responseText;
			
			if ( this.processor )
				this.processor(this);
		}
	}
}
AJAX.prototype.get_obj = function()
{
	var obj = null;
	
	// find native XMLHttpRequest object
	if ( window.XMLHttpRequest )
	{
		try { obj = new XMLHttpRequest(); }
		catch (e) { obj = null; }
	}
	else if ( window.ActiveXObject )
	{
		try { obj = new ActiveXObject("Msxml2.XMLHTTP"); }
		catch (e)
		{
			try { obj = new ActiveXObject("Microsoft.XMLHTTP"); }
			catch (e) { obj = null; }
		}
	}
	
	return obj;
}
AJAX.prototype.process_data = function()
{
	if ( this.obj.readyState == 4 ) // the object is finished loading
	{
		if ( this.obj.status == 200 ) // the object is valid
		{
			this.xml = this.obj.responseXML;
			this.text = this.obj.responseText;
			
			if ( this.processor )
				this.processor(this);
		}
		else
			alert( "There was a problem retrieving the XML data:\n" + this.statusText );
	}
}
AJAX.prototype.serialize_form = function()
{
	var temp_data = [];
	
	for ( var i = 0; i < this.form.elements.length; i++ )
	{
		if ( this.form.elements[i].type == 'radio' )
		{
			if ( this.form.elements[i].checked )
				temp_data.push( this.form.elements[i].name + "=" + encodeURI(this.form.elements[i].value) );
			continue;
		}
		if ( this.form.elements[i].type == "checkbox" && !this.form.elements[i].checked )
			temp_data.push( this.form.elements[i].name + "=0" );
		else if ( this.form.elements[i].type == "select-multiple" )
		{
			for ( j = 0; j < this.form.elements[i].options.length; j++ )
			{
				if ( this.form.elements[i].options[j].selected )
					temp_data.push( this.form.elements[i].name + "=" + encodeURI(this.form.elements[i].options[j].value) );
			}
		}
		else
			temp_data.push( this.form.elements[i].name + "=" + encodeURI(this.form.elements[i].value) );
	}
	
	this.data = temp_data.join("&");
}