3 * version: 2.21 (08-FEB-2009)
4 * @requires jQuery v1.2.2 or later
6 * Examples and documentation at: http://malsup.com/jquery/form/
7 * Dual licensed under the MIT and GPL licenses:
8 * http://www.opensource.org/licenses/mit-license.php
9 * http://www.gnu.org/licenses/gpl.html
16 Do not use both ajaxSubmit and ajaxForm on the same form. These
17 functions are intended to be exclusive. Use ajaxSubmit if you want
18 to bind your own submit handler to the form. For example,
20 $(document).ready(function() {
21 $('#myForm').bind('submit', function() {
25 return false; // <-- important!
29 Use ajaxForm when you want the plugin to manage all the event binding
32 $(document).ready(function() {
33 $('#myForm').ajaxForm({
38 When using ajaxForm, the ajaxSubmit function will be invoked for you
39 at the appropriate time.
43 * ajaxSubmit() provides a mechanism for immediately submitting
44 * an HTML form using AJAX.
46 $.fn
.ajaxSubmit = function(options
) {
47 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
49 log('ajaxSubmit: skipping submit process - no element selected');
53 if (typeof options
== 'function')
54 options
= { success
: options
};
57 url
: this.attr('action') || window
.location
.toString(),
58 type
: this.attr('method') || 'GET'
61 // hook for manipulating the form data before it is extracted;
62 // convenient for use with rich editors like tinyMCE or FCKEditor
64 this.trigger('form-pre-serialize', [this, options
, veto
]);
66 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
70 // provide opportunity to alter form data before it is serialized
71 if (options
.beforeSerialize
&& options
.beforeSerialize(this, options
) === false) {
72 log('ajaxSubmit: submit aborted via beforeSerialize callback');
76 var a
= this.formToArray(options
.semantic
);
78 options
.extraData
= options
.data
;
79 for (var n
in options
.data
) {
80 if(options
.data
[n
] instanceof Array
) {
81 for (var k
in options
.data
[n
])
82 a
.push( { name
: n
, value
: options
.data
[n
][k
] } )
85 a
.push( { name
: n
, value
: options
.data
[n
] } );
89 // give pre-submit callback an opportunity to abort the submit
90 if (options
.beforeSubmit
&& options
.beforeSubmit(a
, this, options
) === false) {
91 log('ajaxSubmit: submit aborted via beforeSubmit callback');
95 // fire vetoable 'validate' event
96 this.trigger('form-submit-validate', [a
, this, options
, veto
]);
98 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
104 if (options
.type
.toUpperCase() == 'GET') {
105 options
.url
+= (options
.url
.indexOf('?') >= 0 ? '&' : '?') + q
;
106 options
.data
= null; // data is null for 'get'
109 options
.data
= q
; // data is the query string for 'post'
111 var $form
= this, callbacks
= [];
112 if (options
.resetForm
) callbacks
.push(function() { $form
.resetForm(); });
113 if (options
.clearForm
) callbacks
.push(function() { $form
.clearForm(); });
115 // perform a load on the target only if dataType is not provided
116 if (!options
.dataType
&& options
.target
) {
117 var oldSuccess
= options
.success
|| function(){};
118 callbacks
.push(function(data
) {
119 $(options
.target
).html(data
).each(oldSuccess
, arguments
);
122 else if (options
.success
)
123 callbacks
.push(options
.success
);
125 options
.success = function(data
, status
) {
126 for (var i
=0, max
=callbacks
.length
; i
< max
; i
++)
127 callbacks
[i
].apply(options
, [data
, status
, $form
]);
130 // are there files to upload?
131 var files
= $('input:file', this).fieldValue();
133 for (var j
=0; j
< files
.length
; j
++)
137 // options.iframe allows user to force iframe mode
138 if (options
.iframe
|| found
) {
139 // hack to fix Safari hang (thanks to Tim Molendijk for this)
140 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
141 if (options
.closeKeepAlive
)
142 $.get(options
.closeKeepAlive
, fileUpload
);
149 // fire 'notify' event
150 this.trigger('form-submit-notify', [this, options
]);
154 // private function for handling file uploads (hat tip to YAHOO!)
155 function fileUpload() {
158 if ($(':input[name=submit]', form
).length
) {
159 alert('Error: Form elements must not be named "submit".');
163 var opts
= $.extend({}, $.ajaxSettings
, options
);
164 var s
= jQuery
.extend(true, {}, $.extend(true, {}, $.ajaxSettings
), opts
);
166 var id
= 'jqFormIO' + (new Date().getTime());
167 var $io
= $('<iframe id="' + id
+ '" name="' + id
+ '" src="about:blank" />');
170 $io
.css({ position
: 'absolute', top
: '-1000px', left
: '-1000px' });
172 var xhr
= { // mock object
178 getAllResponseHeaders: function() {},
179 getResponseHeader: function() {},
180 setRequestHeader: function() {},
183 $io
.attr('src','about:blank'); // abort op in progress
188 // trigger ajax global events so that activity/block indicators work like normal
189 if (g
&& ! $.active
++) $.event
.trigger("ajaxStart");
190 if (g
) $.event
.trigger("ajaxSend", [xhr
, opts
]);
192 if (s
.beforeSend
&& s
.beforeSend(xhr
, s
) === false) {
193 s
.global
&& jQuery
.active
--;
202 // add submitting element to data if we know it
206 if (n
&& !sub
.disabled
) {
207 options
.extraData
= options
.extraData
|| {};
208 options
.extraData
[n
] = sub
.value
;
209 if (sub
.type
== "image") {
210 options
.extraData
[name
+'.x'] = form
.clk_x
;
211 options
.extraData
[name
+'.y'] = form
.clk_y
;
216 // take a breath so that pending repaints get some cpu time before the upload starts
217 setTimeout(function() {
218 // make sure form attrs are set
219 var t
= $form
.attr('target'), a
= $form
.attr('action');
221 // update form attrs in IE friendly way
222 form
.setAttribute('target',id
);
223 if (form
.getAttribute('method') != 'POST')
224 form
.setAttribute('method', 'POST');
225 if (form
.getAttribute('action') != opts
.url
)
226 form
.setAttribute('action', opts
.url
);
228 // ie borks in some cases when setting encoding
229 if (! options
.skipEncodingOverride
) {
231 encoding
: 'multipart/form-data',
232 enctype
: 'multipart/form-data'
238 setTimeout(function() { timedOut
= true; cb(); }, opts
.timeout
);
240 // add "extra" data to form if provided in options
241 var extraInputs
= [];
243 if (options
.extraData
)
244 for (var n
in options
.extraData
)
246 $('<input type="hidden" name="'+n
+'" value="'+options
.extraData
[n
]+'" />')
249 // add iframe to doc and submit the form
250 $io
.appendTo('body');
251 io
.attachEvent
? io
.attachEvent('onload', cb
) : io
.addEventListener('load', cb
, false);
255 // reset attrs and remove "extra" input elements
256 form
.setAttribute('action',a
);
257 t
? form
.setAttribute('target', t
) : $form
.removeAttr('target');
258 $(extraInputs
).remove();
262 var nullCheckFlag
= 0;
265 if (cbInvoked
++) return;
267 io
.detachEvent
? io
.detachEvent('onload', cb
) : io
.removeEventListener('load', cb
, false);
271 if (timedOut
) throw 'timeout';
272 // extract the server response from the iframe
275 doc
= io
.contentWindow
? io
.contentWindow
.document
: io
.contentDocument
? io
.contentDocument
: io
.document
;
277 if ((doc
.body
== null || doc
.body
.innerHTML
== '') && !nullCheckFlag
) {
278 // in some browsers (cough, Opera 9.2.x) the iframe DOM is not always traversable when
279 // the onload callback fires, so we give them a 2nd chance
286 xhr
.responseText
= doc
.body
? doc
.body
.innerHTML
: null;
287 xhr
.responseXML
= doc
.XMLDocument
? doc
.XMLDocument
: doc
;
288 xhr
.getResponseHeader = function(header
){
289 var headers
= {'content-type': opts
.dataType
};
290 return headers
[header
];
293 if (opts
.dataType
== 'json' || opts
.dataType
== 'script') {
294 var ta
= doc
.getElementsByTagName('textarea')[0];
295 xhr
.responseText
= ta
? ta
.value
: xhr
.responseText
;
297 else if (opts
.dataType
== 'xml' && !xhr
.responseXML
&& xhr
.responseText
!= null) {
298 xhr
.responseXML
= toXml(xhr
.responseText
);
300 data
= $.httpData(xhr
, opts
.dataType
);
304 $.handleError(opts
, xhr
, 'error', e
);
307 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
309 opts
.success(data
, 'success');
310 if (g
) $.event
.trigger("ajaxSuccess", [xhr
, opts
]);
312 if (g
) $.event
.trigger("ajaxComplete", [xhr
, opts
]);
313 if (g
&& ! --$.active
) $.event
.trigger("ajaxStop");
314 if (opts
.complete
) opts
.complete(xhr
, ok
? 'success' : 'error');
317 setTimeout(function() {
319 xhr
.responseXML
= null;
323 function toXml(s
, doc
) {
324 if (window
.ActiveXObject
) {
325 doc
= new ActiveXObject('Microsoft.XMLDOM');
330 doc
= (new DOMParser()).parseFromString(s
, 'text/xml');
331 return (doc
&& doc
.documentElement
&& doc
.documentElement
.tagName
!= 'parsererror') ? doc
: null;
337 * ajaxForm() provides a mechanism for fully automating form submission.
339 * The advantages of using this method instead of ajaxSubmit() are:
341 * 1: This method will include coordinates for <input type="image" /> elements (if the element
342 * is used to submit the form).
343 * 2. This method will include the submit element's name/value data (for the element that was
344 * used to submit the form).
345 * 3. This method binds the submit() method to the form for you.
347 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
348 * passes the options argument along after properly binding events for submit elements and
351 $.fn
.ajaxForm = function(options
) {
352 return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
353 $(this).ajaxSubmit(options
);
356 // store options in hash
357 $(":submit,input:image", this).bind('click.form-plugin',function(e
) {
358 var form
= this.form
;
360 if (this.type
== 'image') {
361 if (e
.offsetX
!= undefined) {
362 form
.clk_x
= e
.offsetX
;
363 form
.clk_y
= e
.offsetY
;
364 } else if (typeof $.fn
.offset
== 'function') { // try to use dimensions plugin
365 var offset
= $(this).offset();
366 form
.clk_x
= e
.pageX
- offset
.left
;
367 form
.clk_y
= e
.pageY
- offset
.top
;
369 form
.clk_x
= e
.pageX
- this.offsetLeft
;
370 form
.clk_y
= e
.pageY
- this.offsetTop
;
374 setTimeout(function() { form
.clk
= form
.clk_x
= form
.clk_y
= null; }, 10);
379 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
380 $.fn
.ajaxFormUnbind = function() {
381 this.unbind('submit.form-plugin');
382 return this.each(function() {
383 $(":submit,input:image", this).unbind('click.form-plugin');
389 * formToArray() gathers form element data into an array of objects that can
390 * be passed to any of the following ajax functions: $.get, $.post, or load.
391 * Each object in the array has both a 'name' and 'value' property. An example of
392 * an array for a simple login form might be:
394 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
396 * It is this array that is passed to pre-submit callback functions provided to the
397 * ajaxSubmit() and ajaxForm() methods.
399 $.fn
.formToArray = function(semantic
) {
401 if (this.length
== 0) return a
;
404 var els
= semantic
? form
.getElementsByTagName('*') : form
.elements
;
406 for(var i
=0, max
=els
.length
; i
< max
; i
++) {
411 if (semantic
&& form
.clk
&& el
.type
== "image") {
412 // handle image inputs on the fly when semantic == true
413 if(!el
.disabled
&& form
.clk
== el
)
414 a
.push({name
: n
+'.x', value
: form
.clk_x
}, {name
: n
+'.y', value
: form
.clk_y
});
418 var v
= $.fieldValue(el
, true);
419 if (v
&& v
.constructor == Array
) {
420 for(var j
=0, jmax
=v
.length
; j
< jmax
; j
++)
421 a
.push({name
: n
, value
: v
[j
]});
423 else if (v
!== null && typeof v
!= 'undefined')
424 a
.push({name
: n
, value
: v
});
427 if (!semantic
&& form
.clk
) {
428 // input type=='image' are not found in elements array! handle them here
429 var inputs
= form
.getElementsByTagName("input");
430 for(var i
=0, max
=inputs
.length
; i
< max
; i
++) {
431 var input
= inputs
[i
];
433 if(n
&& !input
.disabled
&& input
.type
== "image" && form
.clk
== input
)
434 a
.push({name
: n
+'.x', value
: form
.clk_x
}, {name
: n
+'.y', value
: form
.clk_y
});
441 * Serializes form data into a 'submittable' string. This method will return a string
442 * in the format: name1=value1&name2=value2
444 $.fn
.formSerialize = function(semantic
) {
445 //hand off to jQuery.param for proper encoding
446 return $.param(this.formToArray(semantic
));
450 * Serializes all field elements in the jQuery object into a query string.
451 * This method will return a string in the format: name1=value1&name2=value2
453 $.fn
.fieldSerialize = function(successful
) {
455 this.each(function() {
458 var v
= $.fieldValue(this, successful
);
459 if (v
&& v
.constructor == Array
) {
460 for (var i
=0,max
=v
.length
; i
< max
; i
++)
461 a
.push({name
: n
, value
: v
[i
]});
463 else if (v
!== null && typeof v
!= 'undefined')
464 a
.push({name
: this.name
, value
: v
});
466 //hand off to jQuery.param for proper encoding
471 * Returns the value(s) of the element in the matched set. For example, consider the following form:
474 * <input name="A" type="text" />
475 * <input name="A" type="text" />
476 * <input name="B" type="checkbox" value="B1" />
477 * <input name="B" type="checkbox" value="B2"/>
478 * <input name="C" type="radio" value="C1" />
479 * <input name="C" type="radio" value="C2" />
482 * var v = $(':text').fieldValue();
483 * // if no values are entered into the text inputs
485 * // if values entered into the text inputs are 'foo' and 'bar'
488 * var v = $(':checkbox').fieldValue();
489 * // if neither checkbox is checked
491 * // if both checkboxes are checked
494 * var v = $(':radio').fieldValue();
495 * // if neither radio is checked
497 * // if first radio is checked
500 * The successful argument controls whether or not the field element must be 'successful'
501 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
502 * The default value of the successful argument is true. If this value is false the value(s)
503 * for each element is returned.
505 * Note: This method *always* returns an array. If no valid value can be determined the
506 * array will be empty, otherwise it will contain one or more values.
508 $.fn
.fieldValue = function(successful
) {
509 for (var val
=[], i
=0, max
=this.length
; i
< max
; i
++) {
511 var v
= $.fieldValue(el
, successful
);
512 if (v
=== null || typeof v
== 'undefined' || (v
.constructor == Array
&& !v
.length
))
514 v
.constructor == Array
? $.merge(val
, v
) : val
.push(v
);
520 * Returns the value of the field element.
522 $.fieldValue = function(el
, successful
) {
523 var n
= el
.name
, t
= el
.type
, tag
= el
.tagName
.toLowerCase();
524 if (typeof successful
== 'undefined') successful
= true;
526 if (successful
&& (!n
|| el
.disabled
|| t
== 'reset' || t
== 'button' ||
527 (t
== 'checkbox' || t
== 'radio') && !el
.checked
||
528 (t
== 'submit' || t
== 'image') && el
.form
&& el
.form
.clk
!= el
||
529 tag
== 'select' && el
.selectedIndex
== -1))
532 if (tag
== 'select') {
533 var index
= el
.selectedIndex
;
534 if (index
< 0) return null;
535 var a
= [], ops
= el
.options
;
536 var one
= (t
== 'select-one');
537 var max
= (one
? index
+1 : ops
.length
);
538 for(var i
=(one
? index
: 0); i
< max
; i
++) {
542 if (!v
) // extra pain for IE...
543 v
= (op
.attributes
&& op
.attributes
['value'] && !(op
.attributes
['value'].specified
)) ? op
.text
: op
.value
;
554 * Clears the form data. Takes the following actions on the form's input fields:
555 * - input text fields will have their 'value' property set to the empty string
556 * - select elements will have their 'selectedIndex' property set to -1
557 * - checkbox and radio inputs will have their 'checked' property set to false
558 * - inputs of type submit, button, reset, and hidden will *not* be effected
559 * - button elements will *not* be effected
561 $.fn
.clearForm = function() {
562 return this.each(function() {
563 $('input,select,textarea', this).clearFields();
568 * Clears the selected form elements.
570 $.fn
.clearFields
= $.fn
.clearInputs = function() {
571 return this.each(function() {
572 var t
= this.type
, tag
= this.tagName
.toLowerCase();
573 if (t
== 'text' || t
== 'password' || tag
== 'textarea')
575 else if (t
== 'checkbox' || t
== 'radio')
576 this.checked
= false;
577 else if (tag
== 'select')
578 this.selectedIndex
= -1;
583 * Resets the form data. Causes all form elements to be reset to their original value.
585 $.fn
.resetForm = function() {
586 return this.each(function() {
587 // guard against an input with the name of 'reset'
588 // note that IE reports the reset function as an 'object'
589 if (typeof this.reset
== 'function' || (typeof this.reset
== 'object' && !this.reset
.nodeType
))
595 * Enables or disables any matching elements.
597 $.fn
.enable = function(b
) {
598 if (b
== undefined) b
= true;
599 return this.each(function() {
605 * Checks/unchecks any matching checkboxes or radio buttons and
606 * selects/deselects and matching option elements.
608 $.fn
.selected = function(select
) {
609 if (select
== undefined) select
= true;
610 return this.each(function() {
612 if (t
== 'checkbox' || t
== 'radio')
613 this.checked
= select
;
614 else if (this.tagName
.toLowerCase() == 'option') {
615 var $sel
= $(this).parent('select');
616 if (select
&& $sel
[0] && $sel
[0].type
== 'select-one') {
617 // deselect all other options
618 $sel
.find('option').selected(false);
620 this.selected
= select
;
625 // helper fn for console logging
626 // set $.fn.ajaxSubmit.debug to true to enable debug logging
628 if ($.fn
.ajaxSubmit
.debug
&& window
.console
&& window
.console
.log
)
629 window
.console
.log('[jquery.form] ' + Array
.prototype.join
.call(arguments
,''));