3 * version: 2.17 (06-NOV-2008)
\r
4 * @requires jQuery v1.2.2 or later
\r
6 * Examples and documentation at: http://malsup.com/jquery/form/
\r
7 * Dual licensed under the MIT and GPL licenses:
\r
8 * http://www.opensource.org/licenses/mit-license.php
\r
9 * http://www.gnu.org/licenses/gpl.html
\r
18 Do not use both ajaxSubmit and ajaxForm on the same form. These
\r
19 functions are intended to be exclusive. Use ajaxSubmit if you want
\r
20 to bind your own submit handler to the form. For example,
\r
22 $(document).ready(function() {
\r
23 $('#myForm').bind('submit', function() {
\r
24 $(this).ajaxSubmit({
\r
27 return false; // <-- important!
\r
31 Use ajaxForm when you want the plugin to manage all the event binding
\r
32 for you. For example,
\r
34 $(document).ready(function() {
\r
35 $('#myForm').ajaxForm({
\r
40 When using ajaxForm, the ajaxSubmit function will be invoked for you
\r
41 at the appropriate time.
\r
45 * ajaxSubmit() provides a mechanism for immediately submitting
\r
46 * an HTML form using AJAX.
\r
48 $.fn.ajaxSubmit = function(options) {
\r
49 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
\r
51 log('ajaxSubmit: skipping submit process - no element selected');
\r
55 if (typeof options == 'function')
\r
56 options = { success: options };
\r
58 options = $.extend({
\r
59 url: this.attr('action') || window.location.toString(),
\r
60 type: this.attr('method') || 'GET'
\r
63 // hook for manipulating the form data before it is extracted;
\r
64 // convenient for use with rich editors like tinyMCE or FCKEditor
\r
66 this.trigger('form-pre-serialize', [this, options, veto]);
\r
68 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
\r
72 // provide opportunity to alter form data before it is serialized
\r
73 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
\r
74 log('ajaxSubmit: submit aborted via beforeSerialize callback');
\r
78 var a = this.formToArray(options.semantic);
\r
80 options.extraData = options.data;
\r
81 for (var n in options.data) {
\r
82 if(options.data[n] instanceof Array) {
\r
83 for (var k in options.data[n])
\r
84 a.push( { name: n, value: options.data[n][k] } )
\r
87 a.push( { name: n, value: options.data[n] } );
\r
91 // give pre-submit callback an opportunity to abort the submit
\r
92 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
\r
93 log('ajaxSubmit: submit aborted via beforeSubmit callback');
\r
97 // fire vetoable 'validate' event
\r
98 this.trigger('form-submit-validate', [a, this, options, veto]);
\r
100 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
\r
104 var q = $.param(a);
\r
106 if (options.type.toUpperCase() == 'GET') {
\r
107 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
\r
108 options.data = null; // data is null for 'get'
\r
111 options.data = q; // data is the query string for 'post'
\r
113 var $form = this, callbacks = [];
\r
114 if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
\r
115 if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
\r
117 // perform a load on the target only if dataType is not provided
\r
118 if (!options.dataType && options.target) {
\r
119 var oldSuccess = options.success || function(){};
\r
120 callbacks.push(function(data) {
\r
121 $(options.target).html(data).each(oldSuccess, arguments);
\r
124 else if (options.success)
\r
125 callbacks.push(options.success);
\r
127 options.success = function(data, status) {
\r
128 for (var i=0, max=callbacks.length; i < max; i++)
\r
129 callbacks[i].apply(options, [data, status, $form]);
\r
132 // are there files to upload?
\r
133 var files = $('input:file', this).fieldValue();
\r
135 for (var j=0; j < files.length; j++)
\r
139 // options.iframe allows user to force iframe mode
\r
140 if (options.iframe || found) {
\r
141 // hack to fix Safari hang (thanks to Tim Molendijk for this)
\r
142 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
\r
143 if ($.browser.safari && options.closeKeepAlive)
\r
144 $.get(options.closeKeepAlive, fileUpload);
\r
151 // fire 'notify' event
\r
152 this.trigger('form-submit-notify', [this, options]);
\r
156 // private function for handling file uploads (hat tip to YAHOO!)
\r
157 function fileUpload() {
\r
158 var form = $form[0];
\r
160 if ($(':input[name=submit]', form).length) {
\r
161 alert('Error: Form elements must not be named "submit".');
\r
165 var opts = $.extend({}, $.ajaxSettings, options);
\r
166 var s = jQuery.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
\r
168 var id = 'jqFormIO' + (new Date().getTime());
\r
169 var $io = $('<iframe id="' + id + '" name="' + id + '" />');
\r
172 if ($.browser.msie || $.browser.opera)
\r
173 io.src = 'javascript:false;document.write("");';
\r
174 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
\r
176 var xhr = { // mock object
\r
178 responseText: null,
\r
182 getAllResponseHeaders: function() {},
\r
183 getResponseHeader: function() {},
\r
184 setRequestHeader: function() {},
\r
185 abort: function() {
\r
187 $io.attr('src','about:blank'); // abort op in progress
\r
191 var g = opts.global;
\r
192 // trigger ajax global events so that activity/block indicators work like normal
\r
193 if (g && ! $.active++) $.event.trigger("ajaxStart");
\r
194 if (g) $.event.trigger("ajaxSend", [xhr, opts]);
\r
196 if (s.beforeSend && s.beforeSend(xhr, s) === false) {
\r
197 s.global && jQuery.active--;
\r
206 // add submitting element to data if we know it
\r
207 var sub = form.clk;
\r
210 if (n && !sub.disabled) {
\r
211 options.extraData = options.extraData || {};
\r
212 options.extraData[n] = sub.value;
\r
213 if (sub.type == "image") {
\r
214 options.extraData[name+'.x'] = form.clk_x;
\r
215 options.extraData[name+'.y'] = form.clk_y;
\r
220 // take a breath so that pending repaints get some cpu time before the upload starts
\r
221 setTimeout(function() {
\r
222 // make sure form attrs are set
\r
223 var t = $form.attr('target'), a = $form.attr('action');
\r
230 // ie borks in some cases when setting encoding
\r
231 if (! options.skipEncodingOverride) {
\r
233 encoding: 'multipart/form-data',
\r
234 enctype: 'multipart/form-data'
\r
240 setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
\r
242 // add "extra" data to form if provided in options
\r
243 var extraInputs = [];
\r
245 if (options.extraData)
\r
246 for (var n in options.extraData)
\r
248 $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
\r
249 .appendTo(form)[0]);
\r
251 // add iframe to doc and submit the form
\r
252 $io.appendTo('body');
\r
253 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
\r
257 // reset attrs and remove "extra" input elements
\r
258 $form.attr('action', a);
\r
259 t ? $form.attr('target', t) : $form.removeAttr('target');
\r
260 $(extraInputs).remove();
\r
265 if (cbInvoked++) return;
\r
267 io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
\r
272 if (timedOut) throw 'timeout';
\r
273 // extract the server response from the iframe
\r
276 doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
\r
278 if (doc.body == null && !operaHack && $.browser.opera) {
\r
279 // In Opera 9.2.x the iframe DOM is not always traversable when
\r
280 // the onload callback fires so we give Opera 100ms to right itself
\r
283 setTimeout(cb, 100);
\r
287 xhr.responseText = doc.body ? doc.body.innerHTML : null;
\r
288 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
\r
289 xhr.getResponseHeader = function(header){
\r
290 var headers = {'content-type': opts.dataType};
\r
291 return headers[header];
\r
294 if (opts.dataType == 'json' || opts.dataType == 'script') {
\r
295 var ta = doc.getElementsByTagName('textarea')[0];
\r
296 xhr.responseText = ta ? ta.value : xhr.responseText;
\r
298 else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
\r
299 xhr.responseXML = toXml(xhr.responseText);
\r
301 data = $.httpData(xhr, opts.dataType);
\r
305 $.handleError(opts, xhr, 'error', e);
\r
308 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
\r
310 opts.success(data, 'success');
\r
311 if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
\r
313 if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
\r
314 if (g && ! --$.active) $.event.trigger("ajaxStop");
\r
315 if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
\r
318 setTimeout(function() {
\r
320 xhr.responseXML = null;
\r
324 function toXml(s, doc) {
\r
325 if (window.ActiveXObject) {
\r
326 doc = new ActiveXObject('Microsoft.XMLDOM');
\r
327 doc.async = 'false';
\r
331 doc = (new DOMParser()).parseFromString(s, 'text/xml');
\r
332 return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
\r
338 * ajaxForm() provides a mechanism for fully automating form submission.
\r
340 * The advantages of using this method instead of ajaxSubmit() are:
\r
342 * 1: This method will include coordinates for <input type="image" /> elements (if the element
\r
343 * is used to submit the form).
\r
344 * 2. This method will include the submit element's name/value data (for the element that was
\r
345 * used to submit the form).
\r
346 * 3. This method binds the submit() method to the form for you.
\r
348 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
\r
349 * passes the options argument along after properly binding events for submit elements and
\r
352 $.fn.ajaxForm = function(options) {
\r
353 return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
\r
354 $(this).ajaxSubmit(options);
\r
356 }).each(function() {
\r
357 // store options in hash
\r
358 $(":submit,input:image", this).bind('click.form-plugin',function(e) {
\r
359 var form = this.form;
\r
361 if (this.type == 'image') {
\r
362 if (e.offsetX != undefined) {
\r
363 form.clk_x = e.offsetX;
\r
364 form.clk_y = e.offsetY;
\r
365 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
\r
366 var offset = $(this).offset();
\r
367 form.clk_x = e.pageX - offset.left;
\r
368 form.clk_y = e.pageY - offset.top;
\r
370 form.clk_x = e.pageX - this.offsetLeft;
\r
371 form.clk_y = e.pageY - this.offsetTop;
\r
375 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
\r
380 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
\r
381 $.fn.ajaxFormUnbind = function() {
\r
382 this.unbind('submit.form-plugin');
\r
383 return this.each(function() {
\r
384 $(":submit,input:image", this).unbind('click.form-plugin');
\r
390 * formToArray() gathers form element data into an array of objects that can
\r
391 * be passed to any of the following ajax functions: $.get, $.post, or load.
\r
392 * Each object in the array has both a 'name' and 'value' property. An example of
\r
393 * an array for a simple login form might be:
\r
395 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
\r
397 * It is this array that is passed to pre-submit callback functions provided to the
\r
398 * ajaxSubmit() and ajaxForm() methods.
\r
400 $.fn.formToArray = function(semantic) {
\r
402 if (this.length == 0) return a;
\r
404 var form = this[0];
\r
405 var els = semantic ? form.getElementsByTagName('*') : form.elements;
\r
406 if (!els) return a;
\r
407 for(var i=0, max=els.length; i < max; i++) {
\r
412 if (semantic && form.clk && el.type == "image") {
\r
413 // handle image inputs on the fly when semantic == true
\r
414 if(!el.disabled && form.clk == el)
\r
415 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
\r
419 var v = $.fieldValue(el, true);
\r
420 if (v && v.constructor == Array) {
\r
421 for(var j=0, jmax=v.length; j < jmax; j++)
\r
422 a.push({name: n, value: v[j]});
\r
424 else if (v !== null && typeof v != 'undefined')
\r
425 a.push({name: n, value: v});
\r
428 if (!semantic && form.clk) {
\r
429 // input type=='image' are not found in elements array! handle them here
\r
430 var inputs = form.getElementsByTagName("input");
\r
431 for(var i=0, max=inputs.length; i < max; i++) {
\r
432 var input = inputs[i];
\r
433 var n = input.name;
\r
434 if(n && !input.disabled && input.type == "image" && form.clk == input)
\r
435 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
\r
442 * Serializes form data into a 'submittable' string. This method will return a string
\r
443 * in the format: name1=value1&name2=value2
\r
445 $.fn.formSerialize = function(semantic) {
\r
446 //hand off to jQuery.param for proper encoding
\r
447 return $.param(this.formToArray(semantic));
\r
451 * Serializes all field elements in the jQuery object into a query string.
\r
452 * This method will return a string in the format: name1=value1&name2=value2
\r
454 $.fn.fieldSerialize = function(successful) {
\r
456 this.each(function() {
\r
459 var v = $.fieldValue(this, successful);
\r
460 if (v && v.constructor == Array) {
\r
461 for (var i=0,max=v.length; i < max; i++)
\r
462 a.push({name: n, value: v[i]});
\r
464 else if (v !== null && typeof v != 'undefined')
\r
465 a.push({name: this.name, value: v});
\r
467 //hand off to jQuery.param for proper encoding
\r
472 * Returns the value(s) of the element in the matched set. For example, consider the following form:
\r
475 * <input name="A" type="text" />
\r
476 * <input name="A" type="text" />
\r
477 * <input name="B" type="checkbox" value="B1" />
\r
478 * <input name="B" type="checkbox" value="B2"/>
\r
479 * <input name="C" type="radio" value="C1" />
\r
480 * <input name="C" type="radio" value="C2" />
\r
481 * </fieldset></form>
\r
483 * var v = $(':text').fieldValue();
\r
484 * // if no values are entered into the text inputs
\r
486 * // if values entered into the text inputs are 'foo' and 'bar'
\r
487 * v == ['foo','bar']
\r
489 * var v = $(':checkbox').fieldValue();
\r
490 * // if neither checkbox is checked
\r
492 * // if both checkboxes are checked
\r
493 * v == ['B1', 'B2']
\r
495 * var v = $(':radio').fieldValue();
\r
496 * // if neither radio is checked
\r
498 * // if first radio is checked
\r
501 * The successful argument controls whether or not the field element must be 'successful'
\r
502 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
\r
503 * The default value of the successful argument is true. If this value is false the value(s)
\r
504 * for each element is returned.
\r
506 * Note: This method *always* returns an array. If no valid value can be determined the
\r
507 * array will be empty, otherwise it will contain one or more values.
\r
509 $.fn.fieldValue = function(successful) {
\r
510 for (var val=[], i=0, max=this.length; i < max; i++) {
\r
512 var v = $.fieldValue(el, successful);
\r
513 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
\r
515 v.constructor == Array ? $.merge(val, v) : val.push(v);
\r
521 * Returns the value of the field element.
\r
523 $.fieldValue = function(el, successful) {
\r
524 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
\r
525 if (typeof successful == 'undefined') successful = true;
\r
527 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
\r
528 (t == 'checkbox' || t == 'radio') && !el.checked ||
\r
529 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
\r
530 tag == 'select' && el.selectedIndex == -1))
\r
533 if (tag == 'select') {
\r
534 var index = el.selectedIndex;
\r
535 if (index < 0) return null;
\r
536 var a = [], ops = el.options;
\r
537 var one = (t == 'select-one');
\r
538 var max = (one ? index+1 : ops.length);
\r
539 for(var i=(one ? index : 0); i < max; i++) {
\r
542 // extra pain for IE...
\r
543 var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
\r
554 * Clears the form data. Takes the following actions on the form's input fields:
\r
555 * - input text fields will have their 'value' property set to the empty string
\r
556 * - select elements will have their 'selectedIndex' property set to -1
\r
557 * - checkbox and radio inputs will have their 'checked' property set to false
\r
558 * - inputs of type submit, button, reset, and hidden will *not* be effected
\r
559 * - button elements will *not* be effected
\r
561 $.fn.clearForm = function() {
\r
562 return this.each(function() {
\r
563 $('input,select,textarea', this).clearFields();
\r
568 * Clears the selected form elements.
\r
570 $.fn.clearFields = $.fn.clearInputs = function() {
\r
571 return this.each(function() {
\r
572 var t = this.type, tag = this.tagName.toLowerCase();
\r
573 if (t == 'file' || t == 'text' || t == 'password' || tag == 'textarea')
\r
575 else if (t == 'checkbox' || t == 'radio')
\r
576 this.checked = false;
\r
577 else if (tag == 'select')
\r
578 this.selectedIndex = -1;
\r
583 * Resets the form data. Causes all form elements to be reset to their original value.
\r
585 $.fn.resetForm = function() {
\r
586 return this.each(function() {
\r
587 // guard against an input with the name of 'reset'
\r
588 // note that IE reports the reset function as an 'object'
\r
589 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
\r
595 * Enables or disables any matching elements.
\r
597 $.fn.enable = function(b) {
\r
598 if (b == undefined) b = true;
\r
599 return this.each(function() {
\r
600 this.disabled = !b
\r
605 * Checks/unchecks any matching checkboxes or radio buttons and
\r
606 * selects/deselects and matching option elements.
\r
608 $.fn.selected = function(select) {
\r
609 if (select == undefined) select = true;
\r
610 return this.each(function() {
\r
612 if (t == 'checkbox' || t == 'radio')
\r
613 this.checked = select;
\r
614 else if (this.tagName.toLowerCase() == 'option') {
\r
615 var $sel = $(this).parent('select');
\r
616 if (select && $sel[0] && $sel[0].type == 'select-one') {
\r
617 // deselect all other options
\r
618 $sel.find('option').selected(false);
\r
620 this.selected = select;
\r
625 // helper fn for console logging
\r
626 // set $.fn.ajaxSubmit.debug to true to enable debug logging
\r
628 if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
\r
629 window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
\r