/*
 * jQuery Form Plugin
 * version: 2.18 (06-JAN-2009)
 * @requires jQuery v1.2.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.form.js 6061 2009-01-07 01:43:18Z malsup $
 */
;
( function($) {

	/*
	 * Usage Note: ----------- Do not use both ajaxSubmit and ajaxForm on the
	 * same form. These functions are intended to be exclusive. Use ajaxSubmit
	 * if you want to bind your own submit handler to the form. For example,
	 * 
	 * $(document).ready(function() { $('#myForm').bind('submit', function() {
	 * $(this).ajaxSubmit({ target: '#output' }); return false; // <--
	 * important! }); });
	 * 
	 * Use ajaxForm when you want the plugin to manage all the event binding for
	 * you. For example,
	 * 
	 * $(document).ready(function() { $('#myForm').ajaxForm({ target: '#output'
	 * }); });
	 * 
	 * When using ajaxForm, the ajaxSubmit function will be invoked for you at
	 * the appropriate time.
	 */

	/**
	 * ajaxSubmit() provides a mechanism for immediately submitting an HTML form
	 * using AJAX.
	 */
	$.fn.ajaxSubmit = function(options) {
		// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
		if (!this.length) {
			log('ajaxSubmit: skipping submit process - no element selected');
			return this;
		}

		if (typeof options == 'function')
			options = {
				success :options
			};

		options = $.extend( {
			url :this.attr('action') || window.location.toString(),
			type :this.attr('method') || 'GET'
		}, options || {});

		// hook for manipulating the form data before it is extracted;
		// convenient for use with rich editors like tinyMCE or FCKEditor
		var veto = {};
		this.trigger('form-pre-serialize', [ this, options, veto ]);
		if (veto.veto) {
			log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
			return this;
		}

		// provide opportunity to alter form data before it is serialized
		if (options.beforeSerialize
				&& options.beforeSerialize(this, options) === false) {
			log('ajaxSubmit: submit aborted via beforeSerialize callback');
			return this;
		}

		var a = this.formToArray(options.semantic);
		if (options.data) {
			options.extraData = options.data;
			for ( var n in options.data) {
				if (options.data[n] instanceof Array) {
					for ( var k in options.data[n])
						a.push( {
							name :n,
							value :options.data[n][k]
						})
				} else
					a.push( {
						name :n,
						value :options.data[n]
					});
			}
		}

		// give pre-submit callback an opportunity to abort the submit
		if (options.beforeSubmit
				&& options.beforeSubmit(a, this, options) === false) {
			log('ajaxSubmit: submit aborted via beforeSubmit callback');
			return this;
		}

		// fire vetoable 'validate' event
		this.trigger('form-submit-validate', [ a, this, options, veto ]);
		if (veto.veto) {
			log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
			return this;
		}

		var q = $.param(a);

		if (options.type.toUpperCase() == 'GET') {
			options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
			options.data = null; // data is null for 'get'
		} else
			options.data = q; // data is the query string for 'post'

		var $form = this, callbacks = [];
		if (options.resetForm)
			callbacks.push( function() {
				$form.resetForm();
			});
		if (options.clearForm)
			callbacks.push( function() {
				$form.clearForm();
			});

		// perform a load on the target only if dataType is not provided
		if (!options.dataType && options.target) {
			var oldSuccess = options.success || function() {
			};
			callbacks.push( function(data) {
				$(options.target).html(data).each(oldSuccess, arguments);
			});
		} else if (options.success)
			callbacks.push(options.success);

		options.success = function(data, status) {
			for ( var i = 0, max = callbacks.length; i < max; i++)
				callbacks[i].apply(options, [ data, status, $form ]);
		};

		// are there files to upload?
		var files = $('input:file', this).fieldValue();
		var found = false;
		for ( var j = 0; j < files.length; j++)
			if (files[j])
				found = true;

		// options.iframe allows user to force iframe mode
		if (options.iframe || found) {
			// hack to fix Safari hang (thanks to Tim Molendijk for this)
			// see:
			// http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
			if ($.browser.safari && options.closeKeepAlive)
				$.get(options.closeKeepAlive, fileUpload);
			else
				fileUpload();
		} else
			$.ajax(options);

		// fire 'notify' event
		this.trigger('form-submit-notify', [ this, options ]);
		return this;

		// private function for handling file uploads (hat tip to YAHOO!)
		function fileUpload() {
			var form = $form[0];

			if ($(':input[name=submit]', form).length) {
				alert('Error: Form elements must not be named "submit".');
				return;
			}

			var opts = $.extend( {}, $.ajaxSettings, options);
			var s = jQuery.extend(true, {}, $.extend(true, {}, $.ajaxSettings),
					opts);

			var id = 'jqFormIO' + (new Date().getTime());
			var $io = $('<iframe id="' + id + '" name="' + id + '" />');
			var io = $io[0];

			if ($.browser.msie || $.browser.opera)
				io.src = 'javascript:false;document.write("");';
			$io.css( {
				position :'absolute',
				top :'-1000px',
				left :'-1000px'
			});

			var xhr = { // mock object
				aborted :0,
				responseText :null,
				responseXML :null,
				status :0,
				statusText :'n/a',
				getAllResponseHeaders : function() {
				},
				getResponseHeader : function() {
				},
				setRequestHeader : function() {
				},
				abort : function() {
					this.aborted = 1;
					$io.attr('src', 'about:blank'); // abort op in progress
			}
			};

			var g = opts.global;
			// trigger ajax global events so that activity/block indicators work
			// like normal
			if (g && !$.active++)
				$.event.trigger("ajaxStart");
			if (g)
				$.event.trigger("ajaxSend", [ xhr, opts ]);

			if (s.beforeSend && s.beforeSend(xhr, s) === false) {
				s.global && jQuery.active--;
				return;
			}
			if (xhr.aborted)
				return;

			var cbInvoked = 0;
			var timedOut = 0;

			// add submitting element to data if we know it
			var sub = form.clk;
			if (sub) {
				var n = sub.name;
				if (n && !sub.disabled) {
					options.extraData = options.extraData || {};
					options.extraData[n] = sub.value;
					if (sub.type == "image") {
						options.extraData[name + '.x'] = form.clk_x;
						options.extraData[name + '.y'] = form.clk_y;
					}
				}
			}

			// take a breath so that pending repaints get some cpu time before
			// the upload starts
			setTimeout( function() {
				// make sure form attrs are set
					var t = $form.attr('target'), a = $form.attr('action');
					$form.attr( {
						target :id,
						method :'POST',
						action :opts.url
					});

					// ie borks in some cases when setting encoding
					if (!options.skipEncodingOverride) {
						$form.attr( {
							encoding :'multipart/form-data',
							enctype :'multipart/form-data'
						});
					}

					// support timout
					if (opts.timeout)
						setTimeout( function() {
							timedOut = true;
							cb();
						}, opts.timeout);

					// add "extra" data to form if provided in options
					var extraInputs = [];
					try {
						if (options.extraData)
							for ( var n in options.extraData)
								extraInputs
										.push($(
												'<input type="hidden" name="'
														+ n + '" value="'
														+ options.extraData[n]
														+ '" />')
												.appendTo(form)[0]);

						// add iframe to doc and submit the form
						$io.appendTo('body');
						io.attachEvent ? io.attachEvent('onload', cb) : io
								.addEventListener('load', cb, false);
						form.submit();
					} finally {
						// reset attrs and remove "extra" input elements
						$form.attr('action', a);
						t ? $form.attr('target', t) : $form
								.removeAttr('target');
						$(extraInputs).remove();
					}
				}, 10);

			function cb() {
				if (cbInvoked++)
					return;

				io.detachEvent ? io.detachEvent('onload', cb) : io
						.removeEventListener('load', cb, false);

				var operaHack = 0;
				var ok = true;
				try {
					if (timedOut)
						throw 'timeout';
					// extract the server response from the iframe
					var data, doc;

					doc = io.contentWindow ? io.contentWindow.document
							: io.contentDocument ? io.contentDocument
									: io.document;

					if (doc.body == null && !operaHack && $.browser.opera) {
						// In Opera 9.2.x the iframe DOM is not always
						// traversable when
						// the onload callback fires so we give Opera 100ms to
						// right itself
						operaHack = 1;
						cbInvoked--;
						setTimeout(cb, 100);
						return;
					}

					xhr.responseText = doc.body ? doc.body.innerHTML : null;
					xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
					xhr.getResponseHeader = function(header) {
						var headers = {
							'content-type' :opts.dataType
						};
						return headers[header];
					};

					if (opts.dataType == 'json' || opts.dataType == 'script') {
						var ta = doc.getElementsByTagName('textarea')[0];
						xhr.responseText = ta ? ta.value : xhr.responseText;
					} else if (opts.dataType == 'xml' && !xhr.responseXML
							&& xhr.responseText != null) {
						xhr.responseXML = toXml(xhr.responseText);
					}
					data = $.httpData(xhr, opts.dataType);
				} catch (e) {
					ok = false;
					$.handleError(opts, xhr, 'error', e);
				}

				// ordering of these callbacks/triggers is odd, but that's how
				// $.ajax does it
				if (ok) {
					opts.success(data, 'success');
					if (g)
						$.event.trigger("ajaxSuccess", [ xhr, opts ]);
				}
				if (g)
					$.event.trigger("ajaxComplete", [ xhr, opts ]);
				if (g && !--$.active)
					$.event.trigger("ajaxStop");
				if (opts.complete)
					opts.complete(xhr, ok ? 'success' : 'error');

				// clean up
				setTimeout( function() {
					$io.remove();
					xhr.responseXML = null;
				}, 100);
			}
			;

			function toXml(s, doc) {
				if (window.ActiveXObject) {
					doc = new ActiveXObject('Microsoft.XMLDOM');
					doc.async = 'false';
					doc.loadXML(s);
				} else
					doc = (new DOMParser()).parseFromString(s, 'text/xml');
				return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc
						: null;
			}
			;
		}
		;
	};

	/**
	 * ajaxForm() provides a mechanism for fully automating form submission.
	 * 
	 * The advantages of using this method instead of ajaxSubmit() are:
	 * 
	 * 1: This method will include coordinates for <input type="image" />
	 * elements (if the element is used to submit the form). 2. This method will
	 * include the submit element's name/value data (for the element that was
	 * used to submit the form). 3. This method binds the submit() method to the
	 * form for you.
	 * 
	 * The options argument for ajaxForm works exactly as it does for
	 * ajaxSubmit. ajaxForm merely passes the options argument along after
	 * properly binding events for submit elements and the form itself.
	 */
	$.fn.ajaxForm = function(options) {
		return this.ajaxFormUnbind().bind('submit.form-plugin', function() {
			$(this).ajaxSubmit(options);
			return false;
		}).each( function() {
			// store options in hash
				$(":submit,input:image", this).bind('click.form-plugin',
						function(e) {
							var form = this.form;
							form.clk = this;
							if (this.type == 'image') {
								if (e.offsetX != undefined) {
									form.clk_x = e.offsetX;
									form.clk_y = e.offsetY;
								} else if (typeof $.fn.offset == 'function') { // try
																				// to
																				// use
																				// dimensions
																				// plugin
							var offset = $(this).offset();
							form.clk_x = e.pageX - offset.left;
							form.clk_y = e.pageY - offset.top;
						} else {
							form.clk_x = e.pageX - this.offsetLeft;
							form.clk_y = e.pageY - this.offsetTop;
						}
					}
					// clear form vars
					setTimeout( function() {
						form.clk = form.clk_x = form.clk_y = null;
					}, 10);
				});
			});
	};

	// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
	$.fn.ajaxFormUnbind = function() {
		this.unbind('submit.form-plugin');
		return this.each( function() {
			$(":submit,input:image", this).unbind('click.form-plugin');
		});

	};

	/**
	 * formToArray() gathers form element data into an array of objects that can
	 * be passed to any of the following ajax functions: $.get, $.post, or load.
	 * Each object in the array has both a 'name' and 'value' property. An
	 * example of an array for a simple login form might be:
	 *  [ { name: 'username', value: 'jresig' }, { name: 'password', value:
	 * 'secret' } ]
	 * 
	 * It is this array that is passed to pre-submit callback functions provided
	 * to the ajaxSubmit() and ajaxForm() methods.
	 */
	$.fn.formToArray = function(semantic) {
		var a = [];
		if (this.length == 0)
			return a;

		var form = this[0];
		var els = semantic ? form.getElementsByTagName('*') : form.elements;
		if (!els)
			return a;
		for ( var i = 0, max = els.length; i < max; i++) {
			var el = els[i];
			var n = el.name;
			if (!n)
				continue;

			if (semantic && form.clk && el.type == "image") {
				// handle image inputs on the fly when semantic == true
				if (!el.disabled && form.clk == el)
					a.push( {
						name :n + '.x',
						value :form.clk_x
					}, {
						name :n + '.y',
						value :form.clk_y
					});
				continue;
			}

			var v = $.fieldValue(el, true);
			if (v && v.constructor == Array) {
				for ( var j = 0, jmax = v.length; j < jmax; j++)
					a.push( {
						name :n,
						value :v[j]
					});
			} else if (v !== null && typeof v != 'undefined')
				a.push( {
					name :n,
					value :v
				});
		}

		if (!semantic && form.clk) {
			// input type=='image' are not found in elements array! handle them
			// here
			var inputs = form.getElementsByTagName("input");
			for ( var i = 0, max = inputs.length; i < max; i++) {
				var input = inputs[i];
				var n = input.name;
				if (n && !input.disabled && input.type == "image"
						&& form.clk == input)
					a.push( {
						name :n + '.x',
						value :form.clk_x
					}, {
						name :n + '.y',
						value :form.clk_y
					});
			}
		}
		return a;
	};

	/**
	 * Serializes form data into a 'submittable' string. This method will return
	 * a string in the format: name1=value1&amp;name2=value2
	 */
	$.fn.formSerialize = function(semantic) {
		// hand off to jQuery.param for proper encoding
		return $.param(this.formToArray(semantic));
	};

	/**
	 * Serializes all field elements in the jQuery object into a query string.
	 * This method will return a string in the format:
	 * name1=value1&amp;name2=value2
	 */
	$.fn.fieldSerialize = function(successful) {
		var a = [];
		this.each( function() {
			var n = this.name;
			if (!n)
				return;
			var v = $.fieldValue(this, successful);
			if (v && v.constructor == Array) {
				for ( var i = 0, max = v.length; i < max; i++)
					a.push( {
						name :n,
						value :v[i]
					});
			} else if (v !== null && typeof v != 'undefined')
				a.push( {
					name :this.name,
					value :v
				});
		});
		// hand off to jQuery.param for proper encoding
		return $.param(a);
	};

	/**
	 * Returns the value(s) of the element in the matched set. For example,
	 * consider the following form:
	 * 
	 * <form><fieldset> <input name="A" type="text" /> <input name="A"
	 * type="text" /> <input name="B" type="checkbox" value="B1" /> <input
	 * name="B" type="checkbox" value="B2"/> <input name="C" type="radio"
	 * value="C1" /> <input name="C" type="radio" value="C2" /> </fieldset></form>
	 * 
	 * var v = $(':text').fieldValue(); // if no values are entered into the
	 * text inputs v == ['',''] // if values entered into the text inputs are
	 * 'foo' and 'bar' v == ['foo','bar']
	 * 
	 * var v = $(':checkbox').fieldValue(); // if neither checkbox is checked v
	 * === undefined // if both checkboxes are checked v == ['B1', 'B2']
	 * 
	 * var v = $(':radio').fieldValue(); // if neither radio is checked v ===
	 * undefined // if first radio is checked v == ['C1']
	 * 
	 * The successful argument controls whether or not the field element must be
	 * 'successful' (per
	 * http://www.w3.org/TR/html4/interact/forms.html#successful-controls). The
	 * default value of the successful argument is true. If this value is false
	 * the value(s) for each element is returned.
	 * 
	 * Note: This method *always* returns an array. If no valid value can be
	 * determined the array will be empty, otherwise it will contain one or more
	 * values.
	 */
	$.fn.fieldValue = function(successful) {
		for ( var val = [], i = 0, max = this.length; i < max; i++) {
			var el = this[i];
			var v = $.fieldValue(el, successful);
			if (v === null || typeof v == 'undefined'
					|| (v.constructor == Array && !v.length))
				continue;
			v.constructor == Array ? $.merge(val, v) : val.push(v);
		}
		return val;
	};

	/**
	 * Returns the value of the field element.
	 */
	$.fieldValue = function(el, successful) {
		var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
		if (typeof successful == 'undefined')
			successful = true;

		if (successful
				&& (!n || el.disabled || t == 'reset' || t == 'button'
						|| (t == 'checkbox' || t == 'radio') && !el.checked
						|| (t == 'submit' || t == 'image') && el.form
						&& el.form.clk != el || tag == 'select'
						&& el.selectedIndex == -1))
			return null;

		if (tag == 'select') {
			var index = el.selectedIndex;
			if (index < 0)
				return null;
			var a = [], ops = el.options;
			var one = (t == 'select-one');
			var max = (one ? index + 1 : ops.length);
			for ( var i = (one ? index : 0); i < max; i++) {
				var op = ops[i];
				if (op.selected) {
					// extra pain for IE...
					var v = $.browser.msie
							&& !(op.attributes['value'].specified) ? op.text
							: op.value;
					if (one)
						return v;
					a.push(v);
				}
			}
			return a;
		}
		return el.value;
	};

	/**
	 * Clears the form data. Takes the following actions on the form's input
	 * fields: - input text fields will have their 'value' property set to the
	 * empty string - select elements will have their 'selectedIndex' property
	 * set to -1 - checkbox and radio inputs will have their 'checked' property
	 * set to false - inputs of type submit, button, reset, and hidden will
	 * *not* be effected - button elements will *not* be effected
	 */
	$.fn.clearForm = function() {
		return this.each( function() {
			$('input,select,textarea', this).clearFields();
		});
	};

	/**
	 * Clears the selected form elements.
	 */
	$.fn.clearFields = $.fn.clearInputs = function() {
		return this.each( function() {
			var t = this.type, tag = this.tagName.toLowerCase();
			if (t == 'text' || t == 'password' || tag == 'textarea')
				this.value = '';
			else if (t == 'checkbox' || t == 'radio')
				this.checked = false;
			else if (tag == 'select')
				this.selectedIndex = -1;
		});
	};

	/**
	 * Resets the form data. Causes all form elements to be reset to their
	 * original value.
	 */
	$.fn.resetForm = function() {
		return this.each( function() {
			// guard against an input with the name of 'reset'
				// note that IE reports the reset function as an 'object'
				if (typeof this.reset == 'function'
						|| (typeof this.reset == 'object' && !this.reset.nodeType))
					this.reset();
			});
	};

	/**
	 * Enables or disables any matching elements.
	 */
	$.fn.enable = function(b) {
		if (b == undefined)
			b = true;
		return this.each( function() {
			this.disabled = !b
		});
	};

	/**
	 * Checks/unchecks any matching checkboxes or radio buttons and
	 * selects/deselects and matching option elements.
	 */
	$.fn.selected = function(select) {
		if (select == undefined)
			select = true;
		return this.each( function() {
			var t = this.type;
			if (t == 'checkbox' || t == 'radio')
				this.checked = select;
			else if (this.tagName.toLowerCase() == 'option') {
				var $sel = $(this).parent('select');
				if (select && $sel[0] && $sel[0].type == 'select-one') {
					// deselect all other options
				$sel.find('option').selected(false);
			}
			this.selected = select;
		}
	})	;
	};

	// helper fn for console logging
	// set $.fn.ajaxSubmit.debug to true to enable debug logging
	function log() {
		if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
			window.console.log('[jquery.form] ' + Array.prototype.join.call(
					arguments, ''));
	}
	;

})(jQuery);

