/**
 * MMI_AJAX class
 * version: 1.3.8
 */

/**
 * Create the MMI_AJAX class.
 *
 * This really looks like a function but we return the object created when it is called plus we
 * will be extending the prototype of this function to add class-like properties to it.
 *
 * To set up and send an advanced request use the following process:
 *
 * var request = new MMI_AJAX(); // Returns a new MMI_AJAX instance.
 * // Set/override any custom request properties here...
 * request.setUrl('some-url.php'); // Sets the URL for the request.
 * request.setParams('foo=bar&cat=furry'); // Sets the request parameters using a string.
 * request.send(); // Send the request.
 *
 * OR - For shorter less customized requests you can use:
 *
 * // One-liner. Set up and immediately sends the request
 * new MMI_AJAX('some-url.php', 'foo=bar&cat=furry', 'post', true, {container: 'wait_overlay'}, {container: 'success_update_container'});
 *
 * OR
 *
 * // Quick setup. Does not send the request immediately.
 * new MMI_AJAX('some-url.php', 'foo=bar&cat=furry', 'post', false);
 *
 * None of these parameters are required.
 *
 * @param url string URL to send the request to.
 * @param params string Parameters to parse into var/value pairs for the parameters object.
 * @param method string The request method to use. Usually 'POST' or 'GET'
 * @param send bool True sends the request immediately using the parameters provided and all other
 * existing settings remain at their global values. False does not send the request and allows you
 * to further manipulate the request properties before sending.
 * @param wait_opts string|object Either a string container id or an object defining wait properties
 * @param success_opts string|object Either a string container id or an object defining success
 * properties.
 * @param error_opts string|object Either a string container id or an object defining error
 * properties
 * @param property_object object This object must contain all of the standard properties and methods
 * used to make the MMI_AJAX class run. Outside of the standard properties and methods, as many or
 * as few additional properties and methods can be set as desired.
 * @return MMI_AJAX
 */
function MMI_AJAX(url, params, method, send, wait_opts, success_opts, error_opts, property_object) {
	if(!$P.is_object(property_object)) {
		property_object = MMI_AJAX_DEFAULT_PROPERTIES;
	}
	/*
	 * Initialize the new instance.
	 *
	 * You must use the 'new' keyword when calling MMI_AJAX() or this line will throw an error.
	 */
	this.__initialize(property_object);

	if(url) {
		this.setUrl(url);
	}
	if(params) {
		this.setParams(params);
	}
	if(method) {
		this.method = method;
	}
	if($P.is_object(wait_opts)) {
		for(key in wait_opts) {
			this.wait[key] = wait_opts[key];
		}
	}
	else if($P.is_string(wait_opts)) {
		// Treat wait_opts as the wait_container id.
		this.wait.container = wait_opts;
	}
	if($P.is_object(success_opts)) {
		for(key in wait_opts) {
			this.success[key] = success_opts[key];
		}
	}
	else if($P.is_string(success_opts)) {
		// Treat wait_opts as the wait_container id.
		this.success.container = success_opts;
	}
	if($P.is_object(error_opts)) {
		for(key in error_opts) {
			this.error[key] = error_opts[key];
		}
	}
	else if($P.is_string(error_opts)) {
		// Treat wait_opts as the wait_container id.
		this.wait.container = error_opts;
	}
	if(send === true) {
		this.send();
	}
	return this;
}

/*
 * Adds a global stack container for MMI_AJAX class instances.
 *
 * This is required for internally maintaining the proper scope when posting forms via iFrames.
 */
window.MMI_AJAX_INSTANCE_STACK = {};

/*
 * MMI_AJAX prototype.
 *
 * All the core functionality for the MMI_AJAX class is contained here.
 *
 * @new Once the new instance of MMI_AJAX has been created you can call __setClassProperties() and
 * pass in your own custom set of object properties to quickly overwrite the default values or add
 * entirely new ones of your own. This would probably only need to be used if setting a large number
 * of properties.
 */
MMI_AJAX.prototype = {
	__initialize: function(object_properties) {
		this.__setClassProperties(object_properties);

		// By default, waiting overlay and success utility setup methods are called during setup.
		this.setSetupCallback('wait_action_setup', this.utils.setupWaitingAction, []);
		this.setSetupCallback('success_action_setup', this.utils.setupSuccessAction, []);
		this.setSetupCallback('request_parameter_setup', this.utils.setupParameters, []);
	},
	__clone: function(o) {
		if(typeof o == 'object' || o instanceof Array) {
			var newObj = (o instanceof Array) ? [] : {};
			for(i in o) {
				newObj[i] = this.__clone(o[i]);
			}
			return newObj;
		}
		return o;
	},
	__setClassProperties: function(properties) {
		// Loop through all the properties in the object provided and set them as the properties
		// for this instance of the class.
		/**/
		for(property_name in properties) {
			//this[property_name] = property_value;
			this[property_name] = this.__clone(properties[property_name]);
		}
		/**/
	},
	__getClassProperties: function() {
		/*
		 * Get all the properties of THIS INSTANCE of this class that are NOT methods/functions.
		 */
		var properties = {};
		for(var p in this) {
			if(typeof this[p] != 'function') {
				if($P.is_object(this[p])) {
					properties[p] = Object.clone(this[p]);
				}
				else {
					properties[p] = this[p];
				}
			}
		}
		return properties;
	}
};




/**
 * This object consists of all the default properties and methods that make the MMI_AJAX class work.
 *
 * If no property object is passed in when creating a new instance of MMI_AJAX then this object's
 * properties and methods are used by default.
 */
MMI_AJAX_DEFAULT_PROPERTIES = {
	/**
	 * NON-STANDARD PROPERTY
	 *
	 * @changed - This property changed to a default of 'true' in version 1.3.6
	 */
	allow_simultaneous_requests: true,
	/**
	 * Whether or not to filet/sanitize JSON response data received from an iFrame.
	 * False by default because the data source should be trusted.
	 * Set this to true for a request if the data source is questionable.
	 */
	sanitize_iframe_json_response: false,
	/**
	 * The URL of the request.
	 *
	 * Use setUrl to modify this property.
	 */
	url: '',
	/**
	 * An object containing the parameters to send with the request.
	 *
	 * By default this object will contain an 'attitude' property set to 'json'
	 * Use setParam, setParams, removeParam, clearParams, and resetParams to modify this property.
	 */
	params: {attitude: 'json'},
	/**
	 * A list of callbacks and their optional custom parameters to call prior to actually
	 * creating and sending the request.
	 */
	setup_callbacks: {},
	/**
	 * A list of callbacks and their optional custom parameters to call on success.
	 */
	success_callbacks: {},
	/**
	 * A list of callbacks and their optional custom parameters to call on error.
	 */
	error_callbacks: {},
	/**
	 * A list of callbacks and their optional custom parameters to call on complete.
	 */
	complete_callbacks: {},
	/**
	 * When this is set the response object will be used to update this container element.
	 *
	 * NON-STANDARD PROPERTY
	 */
	success: {
		container: '',
		/**
		 * The action to take on success_container if set. Defaults to 'replace'
		 *
		 * Available options are:
		 *  - replace	> Replaces the entire DOM element with the transport.responseJSON.result value.
		 *  - update	> Updates the contents inside the DOM element with the result value.
		 *  - insert	> Inserts the contents of the result value in relation to the success_container
		 */
		action: 'replace',
		/**
		 * When the success_action is 'insert' you can specify the position of the insertion relative
		 * to the success_container element.
		 *
		 * Available options are:
		 *  - top		> Inserts the content inside the element at the top.
		 *  - bottom	> Inserts the content inside the element at the bottom.
		 *  - before	> Inserts the content outside and before the element.
		 *  - after		> Inserts the content outside and after the element.
		 */
		insert_position: 'bottom'
	},
	/**
	 * When this is set the default action is nothing.
	 *
	 * It is here to be available to custom callbacks
	 *
	 * NON-STANDARD PROPERTY
	 */
	error: {container: ''},
	/**
	 * This property is not a standard property of this class. It is just here as a sample though
	 * it will probably remain as-is. It's current use is simply to aid the utility waiting overlay
	 * methods.
	 *
	 * The reason this property is not standard is that the entire utility object or individual
	 * methods within it could be altered externally. This could occur globally or per instance.
	 *
	 * NON-STANDARD PROPERTY
	 */
	wait: {
		/**
		 * When this is set, a wait loader overlay will be shown on this container element.
		 */
		container: '',
		/**
		 * When this AND wait_container are set, this image is displayed while the request is running.
		 */
		image: '/images/core/ajax-loader.gif',
		/**
		 * This will be used in the creation of the img tag for the loader graphic.
		 *
		 * This needs to be a unique ID.
		 */
		image_container: 'mmi_ajax_loader_icon_graphic',
		/**
		 * The options parameter for the Effect. Fade function when fading in.
		 */
		fade_in_options: {from: 0.5, to: 1.0, duration: 0.25},
		/**
		 * The options parameter for the Effect. Fade function when fading out.
		 */
		fade_out_options: {from: 1.0, to: 0.5, duration: 0.25}
	},
	/**
	 * This property is not a standard property of this class. It is just here as a sample though
	 * it will probably remain as-is. It's current use is simply to aid the utility scale methods.
	 *
	 * The reason this property is not standard is that the entire utility object or individual
	 * methods within it could be altered externally. This could occur globally or per instance.
	 *
	 * NON-STANDARD PROPERTY
	 */
	scale_opts: {
		scaleFrom: 100,
		scaleX: false,
		scaleY: true,
		scaleContent: false,
		scaleFromCenter: false,
		/**
		 * Leave null to calculate automatically using the abs(C / P) * S formula.
		 * C = change in height between new and old containers.
		 * P = significant pixels of change
		 * S = number of seconds it takes to move P (significant pixels)
		 */
		duration: null,
		/**
		 * Significant pixels of change
		 *
		 * What this means is that if the container is changing 25 pixels and the unit of time for
		 * the effect duration is, for example, 1.0 seconds, it would set the duration to 0.25
		 * seconds since it only has to move 25 pixels instead of 100 pixels. According to a 100
		 * pixel animation for a duration of 1 second you would still move 25 pixels in 0.25 seconds
		 *
		 * P defaults to 100 pixels.
		 */
		P: 100,
		/**
		 * Number of seconds it takes to move P (significant pixels)
		 *
		 * S defaults to 0.5 seconds.
		 */
		S: .5,
		/**
		 * Leave this null to set the originalHeight and originalWidth properties internally.
		 *
		 * 'box', 'content' and your own custom object are the other options here.
		 */
		scaleMode: null,
		/**
		 * Leave this null to set the percentage of scale to an internally calculated value.
		 */
		scaleTo: null
	},
	/**
	 * Prototype Ajax Options
	 *
	 * See this page for more details on the following properties:
	 *
	 * http://www.prototypejs.org/api/ajax/options
	 *
	 */
	asynchronous: true,
	contentType: 'application/x-www-form-urlencoded',
	encoding: 'UTF-8',
	method: 'post',
	requestHeaders: null,
	evalJS: true,
	evalJSON: true,
	sanitizeJSON: false,
	// END Prototype Ajax Options
	/**
	 * Send the request.
	 *
	 * @return bool True if the request is successfully started.
	 */
	send: function() {
		if(!this.utils.isReady.apply(this)) {
			// This request is not ready to be sent!
			return false;
		}

		// Run the callbacks from the setup callback stack.
		for(var ref in this.setup_callbacks) {
			callback = this.setup_callbacks[ref];
			callback.fn.apply(this, callback.params);
		}

		// This request is ready so let's continue.
		var request_options = {asynchronous: this.asynchronous,
			contentType: this.contentType,
			encoding: this.encoding,
			method: this.method,
			evalJS: this.evalJS,
			evalJSON: this.evalJSON,
			parameters: this.params,
			onSuccess: this.successHandler.bind(this),
			onFailure: this.errorHandler.bind(this),
			onComplete: this.completeHandler.bind(this)
		};

		// Only set the requestHeaders property in the request if it is not null or empty.
		if(this.requestHeaders != null
				&& this.requestHeaders != ''
				&& $P.is_string(this.requestHeaders)) {
			request_options.requestHeaders = this.requestHeaders;
		}

		// Create and send the request using Prototype.
		new Ajax.Request(this.url, request_options);
		return true;
	},
	/**
	 * This handler funciton is called with the proper scope when the request succeeds.
	 *
	 * Any success_callbacks are run here.
	 *
	 * @param transport The transport generated by Ajax.Request
	 * @return void
	 */
	successHandler: function(transport) {
		// First callback set to run if the request was successful.
		//console.log('successHandler()');
		//alert('Success Handler Scope: ' + this);
		for(var ref in this.success_callbacks) {
			callback = this.success_callbacks[ref];
			callback.params[callback.params.length] = transport;
			callback.fn.apply(this, callback.params);
		}
	},
	/**
	 * This handler funciton is called with the proper scope when the request fails.
	 *
	 * Any error_callbacks are run here.
	 *
	 * @param transport The transport generated by Ajax.Request
	 * @return void
	 */
	errorHandler: function(transport) {
		// First callback set to run if there is an error.
		//console.log('errorHandler()');
		//alert('Error Handler Scope: ' + this);
		for(var ref in this.error_callbacks) {
			callback = this.error_callbacks[ref];
			callback.params[callback.params.length] = transport;
			callback.fn.apply(this, callback.params);
		}
	},
	/**
	 * This handler funciton is called with the proper scope when the request completes.
	 *
	 * Request completion does *not* indicate success or failure.
	 *
	 * Any complete_callbacks are run here.
	 *
	 * @param transport The transport generated by Ajax.Request
	 * @return void
	 */
	completeHandler: function(transport) {
		// Last callback set to run
		//console.log('completeHandler()');
		//alert('Complete Handler Scope: ' + this);
		for(var ref in this.complete_callbacks) {
			callback = this.complete_callbacks[ref];
			callback.params[callback.params.length] = transport;
			callback.fn.apply(this, callback.params);
		}
	},
	/**
	 * Sets the URL for this request instance.
	 *
	 * @param url The URL to send this request to.
	 * @return void
	 */
	setUrl: function(url) {
		this.url = url;
	},
	/**
	 * Gets the URL of this request instance.
	 *
	 * @return string
	 */
	getUrl: function() {
		return this.url;
	},
	/**
	 * Sets/adds a parameter for this request.
	 *
	 * These will be converted into a query string looking like this: foo=bar&var=value
	 *
	 * @param name The name portion of the parameter (i.e. 'foo' or 'var' above.)
	 * @param value The value portion of the parameter (i.e. 'bar' or 'value' above.)
	 * @return void
	 */
	setParam: function(name, value) {
		this.params[name] = value;
	},
	/**
	 * Removes the parameter named from the request parameters if it exists.
	 *
	 * @param name The name of the parameter/property to remove.
	 * @return void
	 */
	removeParam: function(name) {
		delete this.params[name];
	},
	/**
	 * Sets/adds multiple parameters and valus to this request at once.
	 *
	 * The values entered can now include single dimensional arrays. The syntax used is the same as
	 * that used to post checkbox data from a standard HTML form.
	 *
	 * @param names An array of names to add.
	 * @param values An array of values to add that corresponds with the names array.
	 * @return bool True if the length of the names and values arrays are =. False otherwise.
	 */
	setParams: function(names, values) {
  /* setParams: function(parameter_object) > Alternate method signature.
   * setParams: function(query_string) > Altername method signature.
   */
		if($P.is_array(names) && $P.is_array(values)) {
			if(names.length == values.length) {
				for(var i=0; i<names.length; i++) {
					if($P.is_array(values[i])) {
						this.params[names[i] + '[]'] = values[i];
					}
					else {
						this.params[names[i]] = values[i];
					}
				}
				return true;
			}
			else {
				return false;
			}
		}
		else if($P.is_object(names)) {
			for(key in names) {
				if($P.is_array(names[key])) {
					this.params[key + '[]'] = names[key];
				}
				else {
					this.params[key] = names[key];
				}
			}
		}
		else if($P.is_string(names)) {
			// If names is a string, consider it to be a URL query string and parse it.
			// It is assumed the string passed has been properly encoded.
			var vars = names.split('&');
			//var name_set = new Array();
			//var value_set = new Array();
			for(var i=0; i < vars.length; i++) {
				pair = vars[i].split('=');
				// Set the parameter and value.
				this.setParam(pair[0], pair[1]);

				// We could set the names and values into an array to set all params at once.
				//name_set[i] = pair[0];
				//value_set[i] = pair[1];
			}
			// Or you could set them this way if you wanted to. I'm not sure which is faster.
			//this.setParams(name_set, value_set);
		}
		else {
			return false;
		}
	},
	/**
	 * Clears *ALL* parameters from this request including the default 'attitude' parameter.
	 *
	 * @return void
	 */
	clearParams: function() {
		this.params = {};
	},
	/**
	 * Clears all parameters from this request except for the default 'attitude' parameter.
	 *
	 * @return void
	 */
	resetParams: function() {
		this.params = {attitude: 'json'};
	},
	/**
	 * Add/set/change a callback function and/or it's parameters that gets called before the request
	 * is created and sent.
	 *
	 * @param ref The reference name to this callback function. Allows for removal or changes.
	 * @param fn The callback function itself.
	 * @param params Optional array of parameters to send to the callback function.
	 * @return void
	 */
	setSetupCallback: function(ref, fn, params) {
		if(!params) {
			params = [];
		}
		this.setup_callbacks[ref] = {fn: fn, params: params};
	},
	/**
	 * Remove the setup callback referenced by 'ref'
	 *
	 * @param ref The reference name of the callback function to remove.
	 * @return void
	 */
	removeSetupCallback: function(ref) {
		delete this.setup_callbacks[ref];
	},
	/**
	 * Add/set/change a callback function and/or it's parameters that gets called on success.
	 *
	 * @param ref The reference name to this callback function. Allows for removal or changes.
	 * @param fn The callback function itself.
	 * @param params Optional array of parameters to send to the callback function.
	 * @return void
	 */
	setSuccessCallback: function(ref, fn, params) {
		if(!params) {
			params = [];
		}
		this.success_callbacks[ref] = {fn: fn, params: params};
	},
	/**
	 * Remove the success callback referenced by 'ref'
	 *
	 * @param ref The reference name of the callback function to remove.
	 * @return void
	 */
	removeSuccessCallback: function(ref) {
		delete this.success_callbacks[ref];
	},
	/**
	 * Add/set/change a callback function and/or it's parameters that gets called on error.
	 *
	 * @param ref The reference name to this callback function. Allows for removal or changes.
	 * @param fn The callback function itself.
	 * @param params Optional array of parameters to send to the callback function.
	 * @return void
	 */
	setErrorCallback: function(ref, fn, params) {
		if(!params) {
			params = [];
		}
		this.error_callbacks[ref] = {fn: fn, params: params};
	},
	/**
	 * Remove the error callback referenced by 'ref'
	 *
	 * @param ref The reference name of the callback function to remove.
	 * @return void
	 */
	removeErrorCallback: function(ref) {
		delete this.error_callbacks[ref];
	},
	/**
	 * Add/set/change a callback function and/or it's parameters that gets called on complete.
	 *
	 * @param ref The reference name to this callback function. Allows for removal or changes.
	 * @param fn The callback function itself.
	 * @param params Optional array of parameters to send to the callback function.
	 * @return void
	 */
	setCompleteCallback: function(ref, fn, params) {
		if(!params) {
			params = [];
		}
		this.complete_callbacks[ref] = {fn: fn, params: params};
	},
	/**
	 * Remove the complete callback referenced by 'ref'
	 *
	 * @param ref The reference name of the callback function to remove.
	 * @return void
	 */
	removeCompleteCallback: function(ref) {
		delete this.complete_callbacks[ref];
	},
	/**
	 * Send a form via Ajax or post it via iFrame.
	 *
	 * @param form_id string The value of the id attribute for the form to submit.
	 * @return bool True if the form submission starts successfully. False if the form is invalid.
	 */
	sendForm: function(form_id) {
		// Get the form DOM object using Prototype
		var f = $(form_id);
		if(f == undefined) {
			// Invalid form
	    	return false;
		}

		// Get the form values and set them as the parameters.
		var params = f.serialize(true);

		// Default submission mode is ajax.
		var mode = 'ajax';

		// Loop through and check the field types for each form field.
		for(key in params) {
			// We don't want to have to have an id on our form element.
			var item = f[key];

			if(item != undefined) {
				/*
				 * If this item is an FCK edtior field then get the value.
				 * Else if this item is a file field then set the mode to iframe.
				 */
				if(item.getAttribute('flag') == 'html') {
					params[key] = FCKeditorAPI.GetInstance(key).GetHTML();
				}
				else if(item.type == 'file') {
					mode = 'iframe';
				}
			}
		}
		// If the submission mode is still ajax then send the form via a standard ajax request.
		if(mode == 'ajax') {
			this.setUrl(f.readAttribute('action'));
			this.params = params;
			this.send();
		}
		else {
			// Fire up the AIM
			f.appendChild(Builder.node('input',
										{'id': 'iframe',
											'name': 'iframe',
											'type': 'hidden',
											'value': '1'}));

			// Add a hidden field
			f.appendChild(Builder.node('input',
										{'id': 'attitude',
											'name': 'attitude',
											'type': 'hidden',
											'value': 'iframe'}));

			/*
			 * Act like a normal Ajax request here and call the setup callbacks except remove the
			 * 'request_parameter_setup' callback first since it may only mess things up.
			 *
			 * If it is still needed an easy work-around would be to do something like this during the request setup:
			 *
			 * req.setup_callbacks['custom_request_parameter_setup'] = req.setup_callbacks['request_parameter_setup'];
			 *
			 * The line above simply copies the callback object to give it a different name.
			 */
			this.removeSetupCallback('request_parameter_setup');
			/*
			 * Add a new setup callback specific to iframes.
			 *
			 * You can easily override this globally or per instance/request by simply setting the
			 * callback function to an empty function like this:
			 *
			 * req.utils.setupIframe = function(){};
			 */
			this.setSetupCallback('iframe_setup', this.utils.setupIframe, []);
			for(var ref in this.setup_callbacks) {
				callback = this.setup_callbacks[ref];
				callback.fn.apply(this, callback.params);
			}

			// Set the proper complete callback
			this.setCompleteCallback('iframe_upload_complete_handler', this.utils.iFramePostComplete, []);

			// Create a unique reference to this instance. The iFrame will need it.
			var class_reference = 'k_ref' + Math.floor(Math.random() * 99999);
			// Save this instance in a globally available list. The AIM.loaded() method will need it

			// Start the form submission process.
			this.AIM.submit.apply(this, [f,
			                             {'onStart': this.utils.iFramePostStart,
											'k_ref': class_reference}]);

			/*
			 * Thanks to the fact that JavaScript simply copies references rather than entire
			 * objects we know that this method should not cause any memory leak problems.
			 */
			window.MMI_AJAX_INSTANCE_STACK[class_reference] = this;

			// Submit the form.
			f.submit();
			return true;
		}
	},
	/**
	 * Send a form via Ajax or post it via iFrame.
	 *
	 * @param form_id string The value of the id attribute for the form to submit.
	 * @param primary_values_setting string This value is optional. Available options are:
	 * 			'form' (the default if empty) - Sets the form values to override parameter values.
	 * 			'params' - Sets the parameter values (this.params) to override form values.
	 * 			Note: Overrides are only done if there are conflicts between parameter names and
	 * 					form field names.
	 * @return bool True if the form submission starts successfully. False if the form is invalid.
	 */
	sendFormWithParams: function(form_id, primary_values_setting) {
		// Get the form DOM object using Prototype
		var f = $(form_id);
		if(f == undefined) {
			// Invalid form
	    	return false;
		}

		switch(primary_values_setting) {
			case 'params':
				var override_form_values = true;
				break;
			case 'form':
			default:
				var override_values_values = false;
				break;
		}

		// Get the form values and set them as the parameters.
		var form_params = f.serialize(true);

		// Default submission mode is ajax.
		var mode = 'ajax';

		function combineParams(primary_params, secondary_params) {
			// Primary params override secondary params.
			var params = {};
			for(var p in secondary_params) {
				params[p] = this.__clone(secondary_params[p]);
			}
			for(var p in primary_params) {
				params[p] = this.__clone(primary_params[p]);
			}
			return params;
		}

		// Loop through and check the field types for each form field.
		var form_elements = new Array();
		var form_el_count = 0;
		for(key in form_params) {
			// We don't want to have to have an id on our form element.
			var item = f[key];

			if(item != undefined) {
				// This element actually exists - keep track of this in case we have to use AIM
				form_elements[form_el_count] = key;
				form_el_count++;
				/*
				 * If this item is an FCK edtior field then get the value.
				 * Else if this item is a file field then set the mode to iframe.
				 */
				if(item.getAttribute('flag') == 'html') {
					form_params[key] = FCKeditorAPI.GetInstance(key).GetHTML();
				}
			}
		}

		/*
		 * Obtain a list of any file inputs fields.
		 */
		var file_inputs = f.getInputs('file');
		if(file_inputs.length) {
			mode = 'iframe';
			// Also make sure the content type and post method are set correctly.
			Element.writeAttribute(f, {enctype: 'multipart/form-data', method: 'POST'});
		}
		// If the submission mode is still ajax then send the form via a standard ajax request.
		if(mode == 'ajax') {
			this.setUrl(f.readAttribute('action'));
			if(override_form_values === true) {
				// Set the params object so that form values are overridden.
				this.params = combineParams.apply(this, Array(this.params, form_params));
			}
			else {
				// Set the params object so that params are overridden (default).
				this.params = combineParams.apply(this, Array(form_params, this.params));
			}
			this.send();
		}
		else {
			// Fire up the AIM

			// Make sure we set the attitude parameter correctly for this form post.
			//this.setParam('attitude', 'iframe');
			//this.setParam('iframe', '1');
			if($P.in_array('iframe', form_elements)) {
				Element.writeAttribute(f['iframe'], {value: '1'});
			}
			else {
				f.appendChild(Builder.node('input',
						{'id': 'iframe',
							'name': 'iframe',
							'type': 'hidden',
							'value': '1'}));
				form_elements[form_elements.length] = 'iframe';
			}

			if($P.in_array('attitude', form_elements)) {
				Element.writeAttribute(f['attitude'], {value: 'iframe'});
			}
			else {
				f.appendChild(Builder.node('input',
						{'id': 'attitude',
							'name': 'attitude',
							'type': 'hidden',
							'value': 'iframe'}));
				form_elements[form_elements.length] = 'attitude';
			}

			// Add a hidden field for each parameter unless the field exists.
			// If the field exists then determine whether or not override form values is on.
			// If it is on then set the value of the existing form element. If off then skip.

			// Don't allow the iframe and attitude values to be overridden - delete them.
			delete this.params.iframe;
			delete this.params.attitude;

			// Make use of the form_elements list we created earlier. Every item in this list is an
			// actual DOM element.
			for(field_name in this.params) {
				if($P.in_array(field_name, form_elements)) {
					if(override_form_values) {
						// Set the existing form element to the value of the parameter.
						Element.writeAttribute(f[field_name], {value: this.params[field_name]});
					}
					else {
						// Not allowed to override existing form values!
					}
				}
				else {
					// This value is new so let's add a hidden input field to the form.
					f.appendChild(Builder.node('input',
												{
													'id': field_name,
													'name': field_name,
													'type': 'hidden',
													'value': this.params[field_name]}));
				}
			}

			/*
			 * Act like a normal Ajax request here and call the setup callbacks except remove the
			 * 'request_parameter_setup' callback first since it may only mess things up.
			 *
			 * If it is still needed an easy work-around would be to do something like this during the request setup:
			 *
			 * req.setup_callbacks['custom_request_parameter_setup'] = req.setup_callbacks['request_parameter_setup'];
			 *
			 * The line above simply copies the callback object to give it a different name.
			 */
			this.removeSetupCallback('request_parameter_setup');
			/*
			 * Add a new setup callback specific to iframes.
			 *
			 * You can easily override this globally or per instance/request by simply setting the
			 * callback function to an empty function like this:
			 *
			 * req.utils.setupIframe = function(){};
			 */
			this.setSetupCallback('iframe_setup', this.utils.setupIframe, []);
			for(var ref in this.setup_callbacks) {
				callback = this.setup_callbacks[ref];
				callback.fn.apply(this, callback.params);
			}

			// Set the proper complete callback
			//this.setCompleteCallback('iframe_upload_complete_handler', this.utils.iFramePostComplete, []);

			// Set up a complete callback to update/replace/insert the content when it is returned.
			/*if(this.utils.checkContainer(this.success.container)) {
				this.setCompleteCallback('update_container_on_success', this.utils.updateSuccessContainer, []);
			}*/

			// Create a unique reference to this instance. The iFrame will need it.
			var class_reference = 'k_ref' + Math.floor(Math.random() * 99999);
			// Save this instance in a globally available list. The AIM.loaded() method will need it

			this.k_ref = class_reference;
			window.MMI_AJAX_INSTANCE_STACK[class_reference] = this.__getClassProperties();
			//window.MMI_AJAX_INSTANCE_STACK[class_reference] = this;

			// Start the form submission process.
			this.AIM.submit.apply(this, [f,
			                             {'onStart': this.utils.iFramePostStart,
											'k_ref': class_reference}]);

			// Submit the form.
			f.submit();
			return true;
		}
	},
	AIM: {
		/**
		 * Create the hidden iFrame element.
		 *
		 * @param c object The custom iFrame properties for this request.
		 * @return string The value of the id attribute for the new hidden iFrame.
		 */
		frame: function(c) {
		// 'this' = instanceof MMI_AJAX
			var n = 'f' + Math.floor(Math.random() * 99999);
			this.iframe_ref = n;

			var d = document.createElement('DIV');
			d.innerHTML = '<iframe style="width: 1px; height: 1px; display: none;" src="about:blank" id="'+n+'" name="'+n+'" onload="var mmi_ajax_req_'+n+' = new MMI_AJAX(); mmi_ajax_req_'+n+'.AIM.loaded.apply(mmi_ajax_req_'+n+', [\''+n+'\', \''+this.k_ref+'\']);"></iframe>';
			document.body.appendChild(d);

			return n;
		},
		/**
		 * Sets the target attribute of the form to the new hidden iFrame. The return data is sent
		 * to the iFrame instead of the main page preventing a complete reload of the page.
		 *
		 * @param f object The form element as obtained via Prototype.
		 * @param name string The value of the id and name attributes of the new hidden iFrame.
		 * @return void
		 */
		form: function(f, name) {
			//console.log(name);
			f.setAttribute('target', name);
		},
		/**
		 * Submit a form via iFrame.
		 *
		 * @param f object The form element as obtained via Prototype.
		 * @param c object The custom iFrame properties for this request.
		 * @return bool True on success. False on failure.
		 */
		submit: function(f, c) {
			// 'this' = instanceof MMI_AJAX

			this.AIM.form(f, this.AIM.frame.apply(this, [c]));
			if(c && typeof(c.onStart) == 'function') {
				return c.onStart.apply(this, []);
	        }
			else {
				return true;
			}
		},
		/**
		 * This method is called when the iFrame is done loading. It handles any tasks that are
		 * related to an iFrame form submission completing.
		 *
		 * @todo This could probably be improved by not passing the ID to this function and just
		 * storing it inside the instance since the instance scope is preserved properly.
		 *
		 * @param id string The value of the id attribute of the iFrame that finished loading.
		 */
		loaded: function(id, k_ref) {
			for(var p in window.MMI_AJAX_INSTANCE_STACK[k_ref]) {
				if($P.is_object(window.MMI_AJAX_INSTANCE_STACK[k_ref][p])) {
					this[p] = Object.clone(window.MMI_AJAX_INSTANCE_STACK[k_ref][p]);
				}
				else {
					this[p] = window.MMI_AJAX_INSTANCE_STACK[k_ref][p];
				}
			}
			var i = $(id);

			// Delete the reference to this instance in the global stack.
			delete window.MMI_AJAX_INSTANCE_STACK[i.k_ref];
			// Note: 'this' still points to the proper object instance even after deleting the
			// reference item from the stack.
			// This is because JavaScript just copied a reference to this object when it added the
			// item to the reference stack.

			if(i.contentDocument) {
				var d = i.contentDocument;
			}
			else if (i.contentWindow) {
				var d = i.contentWindow.document;
			}
			else {
				var d = window.frames[id].document;
			}
			/*if(d.location.href == "about:blank") {
				// Attempt to remove the iFrame from the page.
				setTimeout(function(){$(id).remove();}, 1000);
				return;
			}*/

			var returned_data = d.body.innerHTML;

			// Save everything into a fake request transport object.
			var transport = {};
			/*
			 * These values have to be faked.
			 * They will be parsed from the JSON response later when error page handling is
			 * added to the server side. (Error page handling must read iframe/attitude params.
			 */
			transport.status = 200;
			transport.statusText = '';
			transport.readyState = 4;
			transport.responseXml = null;
			transport.headerJSON = null;
			transport.request = null;
			transport.transport = null;
			// The following are methods that are not available from this mock response object.
			/*
			 * transport.getHeader(name)
			 * transport.getAllHeaders()
			 * transport.getResponseHeader(name)
			 * transport.getAllResponseHeaders()
			 */

			// Determine what type of response this is.
			if(returned_data.isJSON()) {
				transport.responseText = returned_data;
				transport.responseJSON = returned_data.evalJSON(this.sanitize_iframe_json_response);
			}
			else {
				transport.responseText = returned_data;
				transport.responseJSON = {};
				transport.responseJSON.error = '';
				transport.responseJSON.result = returned_data;
			}

			// Call the onComplete callbacks
			/*for(var ref in this.complete_callbacks) {
				callback = this.complete_callbacks[ref];
				callback.params[callback.params.length] = transport; // mock tranport object
				callback.fn.apply(this, callback.params);
			}*/
			// Treat this as a successful Ajax request.
			// Success callbacks run first.
			this.successHandler.apply(this, [transport]);
			// Complete callbacks run second.
			this.completeHandler.apply(this, [transport]);

			/*else {

				// Call the onComplete callbacks
				for(var ref in this.complete_callbacks) {
					callback = this.complete_callbacks[ref];
					callback.params[callback.params.length] = returned_data; // string
					callback.fn.apply(this, callback.params);
				}
			}*/

			// Attempt to remove the iFrame from the page.
			setTimeout(function(){$(id).remove();}, 1000);
		}
	},
	/**
	 * 'this' inside *any* of the utils.* methods should always point to an instance of the MMI_AJAX
	 * class and not to any other object. Use call(), apply(), and bind() to perpetuate this scope
	 * as necessary.
	 *
	 * This may seem messy or overly complex but using existing object properties instead of passing
	 * parameters is much cleaner in the end.
	 *
	 * These methods have been packaged into a 'utils' object so that they are effectively separated
	 * from the main class. You can easily override these individually or you can override the
	 * entire utils package to provide custom functionality.
	 */
	utils: {
		/**
		 * Make sure the request is ready to be sent.
		 *
		 * REQUIRED METHOD FOR EVERY UTILS PACKAGE!!! The send() method relies on this method.
		 *
		 * @return bool
		 */
		isReady: function() {
			if(this.allow_simultaneous_requests !== true && Ajax.activeRequestCount > 0) {
				// Destroy this request attempt. A request is already in progress.
				return false;
			}
			if(this.url == '' || !$P.is_string(this.url)) {
				// The URL is an empty string or is not a string.
				return false;
			}
			else {
				return true;
			}
		},
		/**
		 * The default method for setting up request parameters.
		 *
		 * This method is called by default since it is set in the default init() method.
		 *
		 * To skip this callback simply use the following before calling send().
		 * request.removeSetupCallback('request_parameter_setup');
		 *
		 * @return void
		 */
		setupParameters: function() {
			// DO NOT set params like this or you will override custom parameters you wanted to send
			// this.params = {};
			if(!this.params.attitude) {
				this.params.attitude = 'json';
			}
		},
		/**
		 * Default iFrame setup callback.
		 *
		 * The only way to 'skip' this method being called when an iFrame post is made is to set it
		 * to an empty function before posting the form.
		 *
		 * var request = new MMI_AJAX();
		 * ...
		 * request.utils.setupIframe = function() {};
		 * request.send();
		 *
		 * @return void
		 */
		setupIframe: function() {
			this.utils.setupIframeParameters.apply(this, []);
		},
		/**
		 * Default method for setting up iFrame parameters.
		 *
		 * Called by default from within MMI_AJAX.utils.setupIframe();
		 *
		 * @return void
		 */
		setupIframeParameters: function() {
			/*if(!this.params.attitude) {
				this.params.attitude = 'iframe';
			}*/
		},
		/**
		 * Default method for checking whether or not a container name is potentially valid.
		 *
		 * @return bool True if the container name is valid. False otherwise.
		 */
		checkContainer: function(container) {
			var valid_container_name = false;
			if(container != '' && $P.is_string(container)) {
				valid_container_name = true;
			}
			return valid_container_name;
		},
		/**
		 * Sets up the waiting overlay and loader icon graphic.
		 *
		 * @return void
		 */
		setupWaitingAction: function() {
			// Only setup the waiting overlay if the wait container is set.
			if(this.utils.checkContainer(this.wait.container)) {
				// Maintain proper scope for 'this'
				this.utils.showWaitingOverlay.apply(this);
				this.setCompleteCallback('wait_container_overlay_removal',
											this.utils.hideWaitingOverlay,
											[]);
			}
		},
		/**
		 * Creates a waiting overlay and displays a loading image if available.
		 *
		 * @return void
		 */
		showWaitingOverlay: function() {
			var c = $(this.wait.container);
			if(c != undefined) {
				c.absolutize();
				c.relativize();
				d = c.getDimensions();

				this.utils.fade_out = new Effect.Fade(c, this.wait.fade_out_options);
				if(this.wait.image && this.wait.image_container) {
					c.insert(Builder.node('img', {'id': this.wait.image_container,
						'src': this.wait.image,
						'style':'z-index:901;position:absolute;left:'
							+(Math.round(d.width/2) - 30)
							+'px;top:'+(Math.round(d.height/2) - 20)+'px;'}));
				}
			}
		},
		/**
		 * Hides the overlay created and shown by showWaitingOverlay()
		 *
		 * @param transport object The transport (created by Prototype) of the request is available
		 * when this is called as a success, error, or complete callback.
		 * @return void
		 */
		hideWaitingOverlay: function(transport) {
			if(this.wait.image && this.wait.image_container) {
				ajax_image = $(this.wait.image_container);
				if(ajax_image != undefined) {
					ajax_image.remove();
				}
			}
			var c = $(this.wait.container);
			if(c != undefined) {
				// Cancel the fade out effect if it is in progress.
				this.utils.fade_out.cancel();
				this.utils.fade_in = new Effect.Fade(c, this.wait.fade_in_options);
			}
		},
		/**
		 * Default success callback method.
		 *
		 * This is set as the callback to the Prototype ajax request internally.
		 * To customize the behavior you can swap out this function like this:
		 *
		 * request.utils.setupSuccessAction = function() {// your custom code };
		 *
		 * @return void
		 */
		setupSuccessAction: function() {
			// Set up a success callback to update/replace/insert the content when it is returned.
			if(this.utils.checkContainer(this.success.container)) {
				this.setSuccessCallback('update_container_on_success', this.utils.updateSuccessContainer, []);
			}
		},
		/**
		 * The default callback to use when a request is successful and has a success.container set.
		 *
		 * Update the container using one of three modes:
		 *  - replace
		 *  - update
		 *  - insert
		 *
		 * The 'insert' mode also has one of four position settings:
		 *  - top
		 *  - bottom
		 *  - above
		 *  - below
		 *
		 * @param transport object The transport (created by Prototype) of the request is available
		 * when this is called as a success, error, or complete callback.
		 * @return void
		 */
		updateSuccessContainer: function(transport) {
			this.utils.updateContainer.apply(this, [this.success.container,
			                                       transport.responseJSON.result,
			                                       this.success.action,
			                                       this.success.insert_position]);
		},
		/**
		 * Updates a container using one of three action modes: replace, update, insert
		 * When using the insert mode a position can be set: top, bottom, above, below
		 *
		 * @param container string The value of the id attribute of the element to update.
		 * @param data string The data/content to update the container element with.
		 * @param action string One of three modes: replace, update, insert
		 * @param insert_position string One of four positions used when inserting:
		 * top, bottom, above, below
		 * @return void
		 */
		updateContainer: function(container, data, action, insert_position) {
			var c = $(container);
			if(!action) {
				action = 'replace';
			}
			switch(action) {
				case 'replace':
					// Replace the entire container
					Element.replace(c, data);
					break;
				case 'scale-replace':
					/*
					 * NOTICE: If the data is plain text with no surrounding HTML container, this
					 * action will NOT work.
					 *
					 * The reason has to do with the fact that the replacement process creates a
					 * copy of this HTML container, gathers information about the copy, then uses
					 * that information when scaling the original container.
					 */
					this.utils.scale.apply(this, [c, data]);
					break;
				case 'update':
					// Update the existing content without replacing the original container
					c.update(data);
					break;
				case 'insert':
					// Insert the content using the position provided.
					// Use 'bottom' if no position set.
					// Available positions are 'top', 'bottom' (INSIDE the element) and 'before', 'after' (OUTSIDE the element)
					if(!insert_position) {
						insert_position = 'bottom';
					}
					var insert_param_obj = {};
					insert_param_obj[insert_position] = data;
					c.insert(insert_param_obj);
					break;
			}
		},
		/**
		 * The default callback method called when an iFrame post begins.
		 *
		 * @return bool
		 */
		iFramePostStart: function() {
			return true;
		},
		/**
		 * The default callback method called when an iFrame post completes.
		 *
		 * This is set automatically as complete_callback and behaves similarly to
		 * MMI_AJAX.utils.updateSuccessContainer() which is the default success callback for ajax
		 * requests.
		 *
		 * @return void
		 */
		iFramePostComplete: function(response) {
			this.utils.updateContainer.apply(this, [this.success.container,
			                                        response,
			                                        this.success.action,
			                                        this.success.insert_position]);
		},
		/**
		 * Scales the content on the X axis to accomodate larger or smaller content blocks.
		 *
		 * Assumes fixed width is desired.
		 *
		 * Uses Effect.Scale()
		 *
		 * @tip You need to make sure you set a CSS float on the container element or the container
		 * 		will not expand and contract when the updated container changes size.
		 * @tip Set a small amount of padding on the element being replaced so that the sizing
		 * 		calculations done inside scriptaculous don't affect the content display. Quite often the
		 * 		calculations done by scriptaculous are not quite accurate visually and parts of letters
		 * 		will display outside the container (or in our case be hidden by the edge of the container)
		 *
		 * @param container string|object Either a string id of an element or an existing prototype
		 * reference to the element.
		 * @param data string HTML content. For this method to work properly the data supplied MUST
		 * to be entirely contained within an HTML element. The HTML container element in the data
		 * MUST have the exact same id as the container supplied. The id attribute is manipulated
		 * internally to avoid id conflicts.
		 * @return void
		 */
		scale: function(container, data) {
			// Rename the existing on-screen container to avoid conflicting IDs.
			var c = $(container);
			/*
			 * We don't know for sure if we got a container object/reference or a string so let's
			 * make sure we have a string ID.
			 */
			container = c.readAttribute('id');
			var new_id = 'old_' + container;
			c.setAttribute('id', new_id);
			c = $(new_id);

			// Obtain the height (and/or width) of the existing container.
			var old_dims = c.getDimensions();

			/*
			 * Create an instance of the new container off-screen so we can get it's properties and
			 * modify it before placing it in the viewable area.
			 */
			var npc = Builder.node('div',
					{id: 'offscreen_data_gathering_element',
					style: 'position: absolute; top: -289375px; left: -923487px;'});
			document.body.appendChild(npc);
			npc = $(npc);
			// Add the request data to the new container.
			npc.update(data);

			// Obtain a reference to the updated content area.
			var uca = $(container);

			// Obtain the height (and/or width) of the new container.
			var new_dims = uca.getDimensions();

			/*
			 * Check the options and settings for scaleTo.
			 */
			if(this.scale_opts.scaleTo === null) {
				// Convert height differences to percentages.
				var diff_percentage = (new_dims.height/old_dims.height)*100;
			}
			else {
				var diff_percentage = this.scale_opts.scaleTo;
			}

			/*
			 * Make sure the overflow style is set to "hidden". If not the new content will spew out all
			 * over the page before the scale effect even starts.
			 *
			 * Set the height (and/or width) of the new container to match the old container.
			 */
			uca.setStyle({display: 'block',
							overflow: 'hidden',
							height: old_dims.height+'px'});

			// Get the HTML generated by the modifications to the new container.
			var new_container_html = npc.innerHTML;

			// Remove the off-screen containers.
			npc.remove();

			// Replace the old container with the new modified container.
			c.replace(new_container_html);

			/*
			 * Now the original (and renamed) container is gone so we need to obtain a reference to the
			 * new container.
			 */
			c = $(container);

			/*
			 * Check the options and settings for duration.
			 */
			if(this.scale_opts.duration === null) {
				// Scaling should take S seconds for every P pixels of C change.
				var height_diff = old_dims.height-new_dims.height;
				// abs(C / P) * S
				var scale_rate = $P.abs(height_diff/this.scale_opts.P)*this.scale_opts.S;
			}
			else {
				var scale_rate = this.scale_opts.duration;
			}

			/*
			 * Check the options and settings for scaleMode.
			 */
			if(this.scale_opts.scaleMode === null) {
				var scale_mode = {originalHeight: old_dims.height,
									originalWidth: old_dims.width};
			}
			else {
				var scale_mode = this.scale_opts.scaleMode;
			}

			/*
			 * Apply the effect to the new container by telling it to resize to it's original dimensions
			 */
			var final_opts = {scaleMode: scale_mode,
								scaleX: this.scale_opts.scaleX,
								scaleY: this.scale_opts.scaleY,
								scaleContent: this.scale_opts.scaleContent,
								scaleFromCenter: this.scale_opts.scaleFromCenter,
								scaleFrom: this.scale_opts.scaleFrom,
								duration: scale_rate};
			new Effect.Scale(c, diff_percentage, final_opts);
		}
	}
};