3 * version: 2.73 (03-MAY-2011)
4 * @requires jQuery v1.3.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(e) {
22 e.preventDefault(); // <-- 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 var action
= this.attr('action');
58 var url
= (typeof action
=== 'string') ? $.trim(action
) : '';
60 // clean url (don't include hash vaue)
61 url
= (url
.match(/^([^#]+)/)||[])[1];
63 url
= url
|| window
.location
.href
|| '';
65 options
= $.extend(true, {
67 success
: $.ajaxSettings
.success
,
68 type
: this[0].getAttribute('method') || 'GET', // IE7 massage (see issue 57)
69 iframeSrc
: /^https/i.test(window
.location
.href
|| '') ? 'javascript:false' : 'about:blank'
72 // hook for manipulating the form data before it is extracted;
73 // convenient for use with rich editors like tinyMCE or FCKEditor
75 this.trigger('form-pre-serialize', [this, options
, veto
]);
77 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
81 // provide opportunity to alter form data before it is serialized
82 if (options
.beforeSerialize
&& options
.beforeSerialize(this, options
) === false) {
83 log('ajaxSubmit: submit aborted via beforeSerialize callback');
87 var n
,v
,a
= this.formToArray(options
.semantic
);
89 options
.extraData
= options
.data
;
90 for (n
in options
.data
) {
91 if(options
.data
[n
] instanceof Array
) {
92 for (var k
in options
.data
[n
]) {
93 a
.push( { name
: n
, value
: options
.data
[n
][k
] } );
98 v
= $.isFunction(v
) ? v() : v
; // if value is fn, invoke it
99 a
.push( { name
: n
, value
: v
} );
104 // give pre-submit callback an opportunity to abort the submit
105 if (options
.beforeSubmit
&& options
.beforeSubmit(a
, this, options
) === false) {
106 log('ajaxSubmit: submit aborted via beforeSubmit callback');
110 // fire vetoable 'validate' event
111 this.trigger('form-submit-validate', [a
, this, options
, veto
]);
113 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
119 if (options
.type
.toUpperCase() == 'GET') {
120 options
.url
+= (options
.url
.indexOf('?') >= 0 ? '&' : '?') + q
;
121 options
.data
= null; // data is null for 'get'
124 options
.data
= q
; // data is the query string for 'post'
127 var $form
= this, callbacks
= [];
128 if (options
.resetForm
) {
129 callbacks
.push(function() { $form
.resetForm(); });
131 if (options
.clearForm
) {
132 callbacks
.push(function() { $form
.clearForm(); });
135 // perform a load on the target only if dataType is not provided
136 if (!options
.dataType
&& options
.target
) {
137 var oldSuccess
= options
.success
|| function(){};
138 callbacks
.push(function(data
) {
139 var fn
= options
.replaceTarget
? 'replaceWith' : 'html';
140 $(options
.target
)[fn
](data
).each(oldSuccess
, arguments
);
143 else if (options
.success
) {
144 callbacks
.push(options
.success
);
147 options
.success = function(data
, status
, xhr
) { // jQuery 1.4+ passes xhr as 3rd arg
148 var context
= options
.context
|| options
; // jQuery 1.4+ supports scope context
149 for (var i
=0, max
=callbacks
.length
; i
< max
; i
++) {
150 callbacks
[i
].apply(context
, [data
, status
, xhr
|| $form
, $form
]);
154 // are there files to upload?
155 var fileInputs
= $('input:file', this).length
> 0;
156 var mp
= 'multipart/form-data';
157 var multipart
= ($form
.attr('enctype') == mp
|| $form
.attr('encoding') == mp
);
159 // options.iframe allows user to force iframe mode
160 // 06-NOV-09: now defaulting to iframe mode if file input is detected
161 if (options
.iframe
!== false && (fileInputs
|| options
.iframe
|| multipart
)) {
162 // hack to fix Safari hang (thanks to Tim Molendijk for this)
163 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
164 if (options
.closeKeepAlive
) {
165 $.get(options
.closeKeepAlive
, fileUpload
);
175 // fire 'notify' event
176 this.trigger('form-submit-notify', [this, options
]);
180 // private function for handling file uploads (hat tip to YAHOO!)
181 function fileUpload() {
184 if ($(':input[name=submit],:input[id=submit]', form
).length
) {
185 // if there is an input with a name or id of 'submit' then we won't be
186 // able to invoke the submit fn on the form (at least not x-browser)
187 alert('Error: Form elements must not have name or id of "submit".');
191 var s
= $.extend(true, {}, $.ajaxSettings
, options
);
192 s
.context
= s
.context
|| s
;
193 var id
= 'jqFormIO' + (new Date().getTime()), fn
= '_'+id
;
194 var $io
= $('<iframe id="' + id
+ '" name="' + id
+ '" src="'+ s
.iframeSrc
+'" />');
197 $io
.css({ position
: 'absolute', top
: '-1000px', left
: '-1000px' });
199 var xhr
= { // mock object
205 getAllResponseHeaders: function() {},
206 getResponseHeader: function() {},
207 setRequestHeader: function() {},
208 abort: function(status
) {
209 var e
= (status
=== 'timeout' ? 'timeout' : 'aborted');
210 log('aborting upload... ' + e
);
212 $io
.attr('src', s
.iframeSrc
); // abort op in progress
214 s
.error
&& s
.error
.call(s
.context
, xhr
, e
, e
);
215 g
&& $.event
.trigger("ajaxError", [xhr
, s
, e
]);
216 s
.complete
&& s
.complete
.call(s
.context
, xhr
, e
);
221 // trigger ajax global events so that activity/block indicators work like normal
222 if (g
&& ! $.active
++) {
223 $.event
.trigger("ajaxStart");
226 $.event
.trigger("ajaxSend", [xhr
, s
]);
229 if (s
.beforeSend
&& s
.beforeSend
.call(s
.context
, xhr
, s
) === false) {
239 var timedOut
= 0, timeoutHandle
;
241 // add submitting element to data if we know it
245 if (n
&& !sub
.disabled
) {
246 s
.extraData
= s
.extraData
|| {};
247 s
.extraData
[n
] = sub
.value
;
248 if (sub
.type
== "image") {
249 s
.extraData
[n
+'.x'] = form
.clk_x
;
250 s
.extraData
[n
+'.y'] = form
.clk_y
;
255 // take a breath so that pending repaints get some cpu time before the upload starts
256 function doSubmit() {
257 // make sure form attrs are set
258 var t
= $form
.attr('target'), a
= $form
.attr('action');
260 // update form attrs in IE friendly way
261 form
.setAttribute('target',id
);
262 if (form
.getAttribute('method') != 'POST') {
263 form
.setAttribute('method', 'POST');
265 if (form
.getAttribute('action') != s
.url
) {
266 form
.setAttribute('action', s
.url
);
269 // ie borks in some cases when setting encoding
270 if (! s
.skipEncodingOverride
) {
272 encoding
: 'multipart/form-data',
273 enctype
: 'multipart/form-data'
279 timeoutHandle
= setTimeout(function() { timedOut
= true; cb(true); }, s
.timeout
);
282 // add "extra" data to form if provided in options
283 var extraInputs
= [];
286 for (var n
in s
.extraData
) {
288 $('<input type="hidden" name="'+n
+'" value="'+s
.extraData
[n
]+'" />')
293 // add iframe to doc and submit the form
294 $io
.appendTo('body');
295 io
.attachEvent
? io
.attachEvent('onload', cb
) : io
.addEventListener('load', cb
, false);
299 // reset attrs and remove "extra" input elements
300 form
.setAttribute('action',a
);
302 form
.setAttribute('target', t
);
304 $form
.removeAttr('target');
306 $(extraInputs
).remove();
314 setTimeout(doSubmit
, 10); // this lets dom updates render
317 var data
, doc
, domCheckCount
= 50, callbackProcessed
;
320 if (xhr
.aborted
|| callbackProcessed
) {
323 if (e
=== true && xhr
) {
324 xhr
.abort('timeout');
328 var doc
= io
.contentWindow
? io
.contentWindow
.document
: io
.contentDocument
? io
.contentDocument
: io
.document
;
329 if (!doc
|| doc
.location
.href
== s
.iframeSrc
) {
330 // response not received yet
334 io
.detachEvent
? io
.detachEvent('onload', cb
) : io
.removeEventListener('load', cb
, false);
342 var isXml
= s
.dataType
== 'xml' || doc
.XMLDocument
|| $.isXMLDoc(doc
);
344 if (!isXml
&& window
.opera
&& (doc
.body
== null || doc
.body
.innerHTML
== '')) {
345 if (--domCheckCount
) {
346 // in some browsers (Opera) the iframe DOM is not always traversable when
347 // the onload callback fires, so we loop a bit to accommodate
348 log('requeing onLoad callback, DOM not available');
352 // let this fall through because server response could be an empty document
353 //log('Could not access iframe DOM after mutiple tries.');
354 //throw 'DOMException: not available';
357 //log('response detected');
358 xhr
.responseText
= doc
.body
? doc
.body
.innerHTML
: doc
.documentElement
? doc
.documentElement
.innerHTML
: null;
359 xhr
.responseXML
= doc
.XMLDocument
? doc
.XMLDocument
: doc
;
362 xhr
.getResponseHeader = function(header
){
363 var headers
= {'content-type': s
.dataType
};
364 return headers
[header
];
367 var scr
= /(json|script|text)/.test(s
.dataType
);
368 if (scr
|| s
.textarea
) {
369 // see if user embedded response in textarea
370 var ta
= doc
.getElementsByTagName('textarea')[0];
372 xhr
.responseText
= ta
.value
;
375 // account for browsers injecting pre around json response
376 var pre
= doc
.getElementsByTagName('pre')[0];
377 var b
= doc
.getElementsByTagName('body')[0];
379 xhr
.responseText
= pre
.textContent
;
382 xhr
.responseText
= b
.innerHTML
;
386 else if (s
.dataType
== 'xml' && !xhr
.responseXML
&& xhr
.responseText
!= null) {
387 xhr
.responseXML
= toXml(xhr
.responseText
);
390 data
= httpData(xhr
, s
.dataType
, s
);
393 log('error caught:',e
);
396 s
.error
&& s
.error
.call(s
.context
, xhr
, 'error', e
);
397 g
&& $.event
.trigger("ajaxError", [xhr
, s
, e
]);
401 log('upload aborted');
405 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
407 s
.success
&& s
.success
.call(s
.context
, data
, 'success', xhr
);
408 g
&& $.event
.trigger("ajaxSuccess", [xhr
, s
]);
411 g
&& $.event
.trigger("ajaxComplete", [xhr
, s
]);
413 if (g
&& ! --$.active
) {
414 $.event
.trigger("ajaxStop");
417 s
.complete
&& s
.complete
.call(s
.context
, xhr
, ok
? 'success' : 'error');
419 callbackProcessed
= true;
421 clearTimeout(timeoutHandle
);
424 setTimeout(function() {
425 $io
.removeData('form-plugin-onload');
427 xhr
.responseXML
= null;
431 var toXml
= $.parseXML
|| function(s
, doc
) { // use parseXML if available (jQuery 1.5+)
432 if (window
.ActiveXObject
) {
433 doc
= new ActiveXObject('Microsoft.XMLDOM');
438 doc
= (new DOMParser()).parseFromString(s
, 'text/xml');
440 return (doc
&& doc
.documentElement
&& doc
.documentElement
.nodeName
!= 'parsererror') ? doc
: null;
442 var parseJSON
= $.parseJSON
|| function(s
) {
443 return window
['eval']('(' + s
+ ')');
446 var httpData = function( xhr
, type
, s
) { // mostly lifted from jq1.4.4
447 var ct
= xhr
.getResponseHeader('content-type') || '',
448 xml
= type
=== 'xml' || !type
&& ct
.indexOf('xml') >= 0,
449 data
= xml
? xhr
.responseXML
: xhr
.responseText
;
451 if (xml
&& data
.documentElement
.nodeName
=== 'parsererror') {
452 $.error
&& $.error('parsererror');
454 if (s
&& s
.dataFilter
) {
455 data
= s
.dataFilter(data
, type
);
457 if (typeof data
=== 'string') {
458 if (type
=== 'json' || !type
&& ct
.indexOf('json') >= 0) {
459 data
= parseJSON(data
);
460 } else if (type
=== "script" || !type
&& ct
.indexOf("javascript") >= 0) {
470 * ajaxForm() provides a mechanism for fully automating form submission.
472 * The advantages of using this method instead of ajaxSubmit() are:
474 * 1: This method will include coordinates for <input type="image" /> elements (if the element
475 * is used to submit the form).
476 * 2. This method will include the submit element's name/value data (for the element that was
477 * used to submit the form).
478 * 3. This method binds the submit() method to the form for you.
480 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
481 * passes the options argument along after properly binding events for submit elements and
484 $.fn
.ajaxForm = function(options
) {
485 // in jQuery 1.3+ we can fix mistakes with the ready state
486 if (this.length
=== 0) {
487 var o
= { s
: this.selector
, c
: this.context
};
488 if (!$.isReady
&& o
.s
) {
489 log('DOM not ready, queuing ajaxForm');
491 $(o
.s
,o
.c
).ajaxForm(options
);
495 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
496 log('terminating; zero elements found by selector' + ($.isReady
? '' : ' (DOM not ready)'));
500 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e
) {
501 if (!e
.isDefaultPrevented()) { // if event has been canceled, don't proceed
503 $(this).ajaxSubmit(options
);
505 }).bind('click.form-plugin', function(e
) {
506 var target
= e
.target
;
508 if (!($el
.is(":submit,input:image"))) {
509 // is this a child element of the submit el? (ex: a span within a button)
510 var t
= $el
.closest(':submit');
518 if (target
.type
== 'image') {
519 if (e
.offsetX
!= undefined) {
520 form
.clk_x
= e
.offsetX
;
521 form
.clk_y
= e
.offsetY
;
522 } else if (typeof $.fn
.offset
== 'function') { // try to use dimensions plugin
523 var offset
= $el
.offset();
524 form
.clk_x
= e
.pageX
- offset
.left
;
525 form
.clk_y
= e
.pageY
- offset
.top
;
527 form
.clk_x
= e
.pageX
- target
.offsetLeft
;
528 form
.clk_y
= e
.pageY
- target
.offsetTop
;
532 setTimeout(function() { form
.clk
= form
.clk_x
= form
.clk_y
= null; }, 100);
536 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
537 $.fn
.ajaxFormUnbind = function() {
538 return this.unbind('submit.form-plugin click.form-plugin');
542 * formToArray() gathers form element data into an array of objects that can
543 * be passed to any of the following ajax functions: $.get, $.post, or load.
544 * Each object in the array has both a 'name' and 'value' property. An example of
545 * an array for a simple login form might be:
547 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
549 * It is this array that is passed to pre-submit callback functions provided to the
550 * ajaxSubmit() and ajaxForm() methods.
552 $.fn
.formToArray = function(semantic
) {
554 if (this.length
=== 0) {
559 var els
= semantic
? form
.getElementsByTagName('*') : form
.elements
;
564 var i
,j
,n
,v
,el
,max
,jmax
;
565 for(i
=0, max
=els
.length
; i
< max
; i
++) {
572 if (semantic
&& form
.clk
&& el
.type
== "image") {
573 // handle image inputs on the fly when semantic == true
574 if(!el
.disabled
&& form
.clk
== el
) {
575 a
.push({name
: n
, value
: $(el
).val()});
576 a
.push({name
: n
+'.x', value
: form
.clk_x
}, {name
: n
+'.y', value
: form
.clk_y
});
581 v
= $.fieldValue(el
, true);
582 if (v
&& v
.constructor == Array
) {
583 for(j
=0, jmax
=v
.length
; j
< jmax
; j
++) {
584 a
.push({name
: n
, value
: v
[j
]});
587 else if (v
!== null && typeof v
!= 'undefined') {
588 a
.push({name
: n
, value
: v
});
592 if (!semantic
&& form
.clk
) {
593 // input type=='image' are not found in elements array! handle it here
594 var $input
= $(form
.clk
), input
= $input
[0];
596 if (n
&& !input
.disabled
&& input
.type
== 'image') {
597 a
.push({name
: n
, value
: $input
.val()});
598 a
.push({name
: n
+'.x', value
: form
.clk_x
}, {name
: n
+'.y', value
: form
.clk_y
});
605 * Serializes form data into a 'submittable' string. This method will return a string
606 * in the format: name1=value1&name2=value2
608 $.fn
.formSerialize = function(semantic
) {
609 //hand off to jQuery.param for proper encoding
610 return $.param(this.formToArray(semantic
));
614 * Serializes all field elements in the jQuery object into a query string.
615 * This method will return a string in the format: name1=value1&name2=value2
617 $.fn
.fieldSerialize = function(successful
) {
619 this.each(function() {
624 var v
= $.fieldValue(this, successful
);
625 if (v
&& v
.constructor == Array
) {
626 for (var i
=0,max
=v
.length
; i
< max
; i
++) {
627 a
.push({name
: n
, value
: v
[i
]});
630 else if (v
!== null && typeof v
!= 'undefined') {
631 a
.push({name
: this.name
, value
: v
});
634 //hand off to jQuery.param for proper encoding
639 * Returns the value(s) of the element in the matched set. For example, consider the following form:
642 * <input name="A" type="text" />
643 * <input name="A" type="text" />
644 * <input name="B" type="checkbox" value="B1" />
645 * <input name="B" type="checkbox" value="B2"/>
646 * <input name="C" type="radio" value="C1" />
647 * <input name="C" type="radio" value="C2" />
650 * var v = $(':text').fieldValue();
651 * // if no values are entered into the text inputs
653 * // if values entered into the text inputs are 'foo' and 'bar'
656 * var v = $(':checkbox').fieldValue();
657 * // if neither checkbox is checked
659 * // if both checkboxes are checked
662 * var v = $(':radio').fieldValue();
663 * // if neither radio is checked
665 * // if first radio is checked
668 * The successful argument controls whether or not the field element must be 'successful'
669 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
670 * The default value of the successful argument is true. If this value is false the value(s)
671 * for each element is returned.
673 * Note: This method *always* returns an array. If no valid value can be determined the
674 * array will be empty, otherwise it will contain one or more values.
676 $.fn
.fieldValue = function(successful
) {
677 for (var val
=[], i
=0, max
=this.length
; i
< max
; i
++) {
679 var v
= $.fieldValue(el
, successful
);
680 if (v
=== null || typeof v
== 'undefined' || (v
.constructor == Array
&& !v
.length
)) {
683 v
.constructor == Array
? $.merge(val
, v
) : val
.push(v
);
689 * Returns the value of the field element.
691 $.fieldValue = function(el
, successful
) {
692 var n
= el
.name
, t
= el
.type
, tag
= el
.tagName
.toLowerCase();
693 if (successful
=== undefined) {
697 if (successful
&& (!n
|| el
.disabled
|| t
== 'reset' || t
== 'button' ||
698 (t
== 'checkbox' || t
== 'radio') && !el
.checked
||
699 (t
== 'submit' || t
== 'image') && el
.form
&& el
.form
.clk
!= el
||
700 tag
== 'select' && el
.selectedIndex
== -1)) {
704 if (tag
== 'select') {
705 var index
= el
.selectedIndex
;
709 var a
= [], ops
= el
.options
;
710 var one
= (t
== 'select-one');
711 var max
= (one
? index
+1 : ops
.length
);
712 for(var i
=(one
? index
: 0); i
< max
; i
++) {
716 if (!v
) { // extra pain for IE...
717 v
= (op
.attributes
&& op
.attributes
['value'] && !(op
.attributes
['value'].specified
)) ? op
.text
: op
.value
;
731 * Clears the form data. Takes the following actions on the form's input fields:
732 * - input text fields will have their 'value' property set to the empty string
733 * - select elements will have their 'selectedIndex' property set to -1
734 * - checkbox and radio inputs will have their 'checked' property set to false
735 * - inputs of type submit, button, reset, and hidden will *not* be effected
736 * - button elements will *not* be effected
738 $.fn
.clearForm = function() {
739 return this.each(function() {
740 $('input,select,textarea', this).clearFields();
745 * Clears the selected form elements.
747 $.fn
.clearFields
= $.fn
.clearInputs = function() {
748 return this.each(function() {
749 var t
= this.type
, tag
= this.tagName
.toLowerCase();
750 if (t
== 'text' || t
== 'password' || tag
== 'textarea') {
753 else if (t
== 'checkbox' || t
== 'radio') {
754 this.checked
= false;
756 else if (tag
== 'select') {
757 this.selectedIndex
= -1;
763 * Resets the form data. Causes all form elements to be reset to their original value.
765 $.fn
.resetForm = function() {
766 return this.each(function() {
767 // guard against an input with the name of 'reset'
768 // note that IE reports the reset function as an 'object'
769 if (typeof this.reset
== 'function' || (typeof this.reset
== 'object' && !this.reset
.nodeType
)) {
776 * Enables or disables any matching elements.
778 $.fn
.enable = function(b
) {
779 if (b
=== undefined) {
782 return this.each(function() {
788 * Checks/unchecks any matching checkboxes or radio buttons and
789 * selects/deselects and matching option elements.
791 $.fn
.selected = function(select
) {
792 if (select
=== undefined) {
795 return this.each(function() {
797 if (t
== 'checkbox' || t
== 'radio') {
798 this.checked
= select
;
800 else if (this.tagName
.toLowerCase() == 'option') {
801 var $sel
= $(this).parent('select');
802 if (select
&& $sel
[0] && $sel
[0].type
== 'select-one') {
803 // deselect all other options
804 $sel
.find('option').selected(false);
806 this.selected
= select
;
811 // helper fn for console logging
812 // set $.fn.ajaxSubmit.debug to true to enable debug logging
814 if ($.fn
.ajaxSubmit
.debug
) {
815 var msg
= '[jquery.form] ' + Array
.prototype.join
.call(arguments
,'');
816 if (window
.console
&& window
.console
.log
) {
817 window
.console
.log(msg
);
819 else if (window
.opera
&& window
.opera
.postError
) {
820 window
.opera
.postError(msg
);