4 * Requires jQuery v1.7.2 or later
5 * Project repository: https://github.com/jquery-form/form
7 * Copyright 2017 Kevin Morris
8 * Copyright 2006 M. Alsup
10 * Dual licensed under the LGPL-2.1+ or MIT licenses
11 * https://github.com/jquery-form/form#license
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 /* global ActiveXObject */
26 if (typeof define
=== 'function' && define
.amd
) {
27 // AMD. Register as an anonymous module.
28 define(['jquery'], factory
);
29 } else if (typeof module
=== 'object' && module
.exports
) {
31 module
.exports = function( root
, jQuery
) {
32 if (typeof jQuery
=== 'undefined') {
33 // require('jQuery') returns a factory that requires window to build a jQuery instance, we normalize how we use modules
34 // that require this pattern but the window provided is a noop if it's defined (how jquery works)
35 if (typeof window
!== 'undefined') {
36 jQuery
= require('jquery');
39 jQuery
= require('jquery')(root
);
57 Do not use both ajaxSubmit and ajaxForm on the same form. These
58 functions are mutually exclusive. Use ajaxSubmit if you want
59 to bind your own submit handler to the form. For example,
61 $(document).ready(function() {
62 $('#myForm').on('submit', function(e) {
63 e.preventDefault(); // <-- important
70 Use ajaxForm when you want the plugin to manage all the event binding
73 $(document).ready(function() {
74 $('#myForm').ajaxForm({
79 You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
80 form does not have to exist when you invoke ajaxForm:
82 $('#myForm').ajaxForm({
87 When using ajaxForm, the ajaxSubmit function will be invoked for you
88 at the appropriate time.
98 feature
.fileapi
= $('<input type="file">').get(0).files
!== undefined;
99 feature
.formdata
= (typeof window
.FormData
!== 'undefined');
101 var hasProp
= !!$.fn
.prop
;
103 // attr2 uses prop when it can but checks the return type for
104 // an expected string. This accounts for the case where a form
105 // contains inputs with names like "action" or "method"; in those
106 // cases "prop" returns the element
107 $.fn
.attr2 = function() {
109 return this.attr
.apply(this, arguments
);
112 var val
= this.prop
.apply(this, arguments
);
114 if ((val
&& val
.jquery
) || typeof val
=== 'string') {
118 return this.attr
.apply(this, arguments
);
122 * ajaxSubmit() provides a mechanism for immediately submitting
123 * an HTML form using AJAX.
125 * @param {object|string} options jquery.form.js parameters or custom url for submission
126 * @param {object} data extraData
127 * @param {string} dataType ajax dataType
128 * @param {function} onSuccess ajax success callback function
130 $.fn
.ajaxSubmit = function(options
, data
, dataType
, onSuccess
) {
131 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
133 log('ajaxSubmit: skipping submit process - no element selected');
138 /* eslint consistent-this: ["error", "$form"] */
139 var method
, action
, url
, $form
= this;
141 if (typeof options
=== 'function') {
142 options
= {success
: options
};
144 } else if (typeof options
=== 'string' || (options
=== false && arguments
.length
> 0)) {
148 'dataType' : dataType
151 if (typeof onSuccess
=== 'function') {
152 options
.success
= onSuccess
;
155 } else if (typeof options
=== 'undefined') {
159 method
= options
.method
|| options
.type
|| this.attr2('method');
160 action
= options
.url
|| this.attr2('action');
162 url
= (typeof action
=== 'string') ? $.trim(action
) : '';
163 url
= url
|| window
.location
.href
|| '';
165 // clean url (don't include hash vaue)
166 url
= (url
.match(/^([^#]+)/) || [])[1];
169 options
= $.extend(true, {
171 success
: $.ajaxSettings
.success
,
172 type
: method
|| $.ajaxSettings
.type
,
173 iframeSrc
: /^https/i.test(window
.location
.href
|| '') ? 'javascript:false' : 'about:blank' // eslint-disable-line no-script-url
176 // hook for manipulating the form data before it is extracted;
177 // convenient for use with rich editors like tinyMCE or FCKEditor
180 this.trigger('form-pre-serialize', [this, options
, veto
]);
183 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
188 // provide opportunity to alter form data before it is serialized
189 if (options
.beforeSerialize
&& options
.beforeSerialize(this, options
) === false) {
190 log('ajaxSubmit: submit aborted via beforeSerialize callback');
195 var traditional
= options
.traditional
;
197 if (typeof traditional
=== 'undefined') {
198 traditional
= $.ajaxSettings
.traditional
;
202 var qx
, a
= this.formToArray(options
.semantic
, elements
, options
.filtering
);
205 var optionsData
= $.isFunction(options
.data
) ? options
.data(a
) : options
.data
;
207 options
.extraData
= optionsData
;
208 qx
= $.param(optionsData
, traditional
);
211 // give pre-submit callback an opportunity to abort the submit
212 if (options
.beforeSubmit
&& options
.beforeSubmit(a
, this, options
) === false) {
213 log('ajaxSubmit: submit aborted via beforeSubmit callback');
218 // fire vetoable 'validate' event
219 this.trigger('form-submit-validate', [a
, this, options
, veto
]);
221 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
226 var q
= $.param(a
, traditional
);
229 q
= (q
? (q
+ '&' + qx
) : qx
);
232 if (options
.type
.toUpperCase() === 'GET') {
233 options
.url
+= (options
.url
.indexOf('?') >= 0 ? '&' : '?') + q
;
234 options
.data
= null; // data is null for 'get'
236 options
.data
= q
; // data is the query string for 'post'
241 if (options
.resetForm
) {
242 callbacks
.push(function() {
247 if (options
.clearForm
) {
248 callbacks
.push(function() {
249 $form
.clearForm(options
.includeHidden
);
253 // perform a load on the target only if dataType is not provided
254 if (!options
.dataType
&& options
.target
) {
255 var oldSuccess
= options
.success
|| function(){};
257 callbacks
.push(function(data
, textStatus
, jqXHR
) {
258 var successArguments
= arguments
,
259 fn
= options
.replaceTarget
? 'replaceWith' : 'html';
261 $(options
.target
)[fn
](data
).each(function(){
262 oldSuccess
.apply(this, successArguments
);
266 } else if (options
.success
) {
267 if ($.isArray(options
.success
)) {
268 $.merge(callbacks
, options
.success
);
270 callbacks
.push(options
.success
);
274 options
.success = function(data
, status
, xhr
) { // jQuery 1.4+ passes xhr as 3rd arg
275 var context
= options
.context
|| this; // jQuery 1.4+ supports scope context
277 for (var i
= 0, max
= callbacks
.length
; i
< max
; i
++) {
278 callbacks
[i
].apply(context
, [data
, status
, xhr
|| $form
, $form
]);
283 var oldError
= options
.error
;
285 options
.error = function(xhr
, status
, error
) {
286 var context
= options
.context
|| this;
288 oldError
.apply(context
, [xhr
, status
, error
, $form
]);
292 if (options
.complete
) {
293 var oldComplete
= options
.complete
;
295 options
.complete = function(xhr
, status
) {
296 var context
= options
.context
|| this;
298 oldComplete
.apply(context
, [xhr
, status
, $form
]);
302 // are there files to upload?
304 // [value] (issue #113), also see comment:
305 // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
306 var fileInputs
= $('input[type=file]:enabled', this).filter(function() {
307 return $(this).val() !== '';
309 var hasFileInputs
= fileInputs
.length
> 0;
310 var mp
= 'multipart/form-data';
311 var multipart
= ($form
.attr('enctype') === mp
|| $form
.attr('encoding') === mp
);
312 var fileAPI
= feature
.fileapi
&& feature
.formdata
;
314 log('fileAPI :' + fileAPI
);
316 var shouldUseFrame
= (hasFileInputs
|| multipart
) && !fileAPI
;
319 // options.iframe allows user to force iframe mode
320 // 06-NOV-09: now defaulting to iframe mode if file input is detected
321 if (options
.iframe
!== false && (options
.iframe
|| shouldUseFrame
)) {
322 // hack to fix Safari hang (thanks to Tim Molendijk for this)
323 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
324 if (options
.closeKeepAlive
) {
325 $.get(options
.closeKeepAlive
, function() {
326 jqxhr
= fileUploadIframe(a
);
330 jqxhr
= fileUploadIframe(a
);
333 } else if ((hasFileInputs
|| multipart
) && fileAPI
) {
334 jqxhr
= fileUploadXhr(a
);
337 jqxhr
= $.ajax(options
);
340 $form
.removeData('jqxhr').data('jqxhr', jqxhr
);
342 // clear element array
343 for (var k
= 0; k
< elements
.length
; k
++) {
347 // fire 'notify' event
348 this.trigger('form-submit-notify', [this, options
]);
352 // utility fn for deep serialization
353 function deepSerialize(extraData
) {
354 var serialized
= $.param(extraData
, options
.traditional
).split('&');
355 var len
= serialized
.length
;
359 for (i
= 0; i
< len
; i
++) {
360 // #252; undo param space replacement
361 serialized
[i
] = serialized
[i
].replace(/\+/g, ' ');
362 part
= serialized
[i
].split('=');
363 // #278; use array instead of object storage, favoring array serializations
364 result
.push([decodeURIComponent(part
[0]), decodeURIComponent(part
[1])]);
370 // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
371 function fileUploadXhr(a
) {
372 var formdata
= new FormData();
374 for (var i
= 0; i
< a
.length
; i
++) {
375 formdata
.append(a
[i
].name
, a
[i
].value
);
378 if (options
.extraData
) {
379 var serializedData
= deepSerialize(options
.extraData
);
381 for (i
= 0; i
< serializedData
.length
; i
++) {
382 if (serializedData
[i
]) {
383 formdata
.append(serializedData
[i
][0], serializedData
[i
][1]);
390 var s
= $.extend(true, {}, $.ajaxSettings
, options
, {
394 type
: method
|| 'POST'
397 if (options
.uploadProgress
) {
398 // workaround because jqXHR does not expose upload property
400 var xhr
= $.ajaxSettings
.xhr();
403 xhr
.upload
.addEventListener('progress', function(event
) {
405 var position
= event
.loaded
|| event
.position
; /* event.position is deprecated */
406 var total
= event
.total
;
408 if (event
.lengthComputable
) {
409 percent
= Math
.ceil(position
/ total
* 100);
412 options
.uploadProgress(event
, position
, total
, percent
);
422 var beforeSend
= s
.beforeSend
;
424 s
.beforeSend = function(xhr
, o
) {
425 // Send FormData() provided by user
426 if (options
.formData
) {
427 o
.data
= options
.formData
;
433 beforeSend
.call(this, xhr
, o
);
440 // private function for handling file uploads (hat tip to YAHOO!)
441 function fileUploadIframe(a
) {
442 var form
= $form
[0], el
, i
, s
, g
, id
, $io
, io
, xhr
, sub
, n
, timedOut
, timeoutHandle
;
443 var deferred
= $.Deferred();
446 deferred
.abort = function(status
) {
451 // ensure that every serialized input is still enabled
452 for (i
= 0; i
< elements
.length
; i
++) {
455 el
.prop('disabled', false);
457 el
.removeAttr('disabled');
462 s
= $.extend(true, {}, $.ajaxSettings
, options
);
463 s
.context
= s
.context
|| s
;
464 id
= 'jqFormIO' + new Date().getTime();
465 var ownerDocument
= form
.ownerDocument
;
466 var $body
= $form
.closest('body');
468 if (s
.iframeTarget
) {
469 $io
= $(s
.iframeTarget
, ownerDocument
);
470 n
= $io
.attr2('name');
472 $io
.attr2('name', id
);
478 $io
= $('<iframe name="' + id
+ '" src="' + s
.iframeSrc
+ '" />', ownerDocument
);
479 $io
.css({position
: 'absolute', top
: '-1000px', left
: '-1000px'});
484 xhr
= { // mock object
490 getAllResponseHeaders : function() {},
491 getResponseHeader : function() {},
492 setRequestHeader : function() {},
493 abort : function(status
) {
494 var e
= (status
=== 'timeout' ? 'timeout' : 'aborted');
496 log('aborting upload... ' + e
);
500 if (io
.contentWindow
.document
.execCommand
) {
501 io
.contentWindow
.document
.execCommand('Stop');
505 $io
.attr('src', s
.iframeSrc
); // abort op in progress
508 s
.error
.call(s
.context
, xhr
, e
, status
);
512 $.event
.trigger('ajaxError', [xhr
, s
, e
]);
516 s
.complete
.call(s
.context
, xhr
, e
);
522 // trigger ajax global events so that activity/block indicators work like normal
523 if (g
&& $.active
++ === 0) {
524 $.event
.trigger('ajaxStart');
527 $.event
.trigger('ajaxSend', [xhr
, s
]);
530 if (s
.beforeSend
&& s
.beforeSend
.call(s
.context
, xhr
, s
) === false) {
545 // add submitting element to data if we know it
549 if (n
&& !sub
.disabled
) {
550 s
.extraData
= s
.extraData
|| {};
551 s
.extraData
[n
] = sub
.value
;
552 if (sub
.type
=== 'image') {
553 s
.extraData
[n
+ '.x'] = form
.clk_x
;
554 s
.extraData
[n
+ '.y'] = form
.clk_y
;
559 var CLIENT_TIMEOUT_ABORT
= 1;
560 var SERVER_ABORT
= 2;
562 function getDoc(frame
) {
563 /* it looks like contentWindow or contentDocument do not
564 * carry the protocol property in ie8, when running under ssl
565 * frame.document is the only valid response document, since
566 * the protocol is know but not on the other two objects. strange?
567 * "Same origin policy" http://en.wikipedia.org/wiki/Same_origin_policy
572 // IE8 cascading access check
574 if (frame
.contentWindow
) {
575 doc
= frame
.contentWindow
.document
;
578 // IE8 access denied under ssl & missing protocol
579 log('cannot get iframe.contentWindow document: ' + err
);
582 if (doc
) { // successful getting content
586 try { // simply checking may throw in ie8 under ssl or mismatched protocol
587 doc
= frame
.contentDocument
? frame
.contentDocument
: frame
.document
;
590 log('cannot get iframe.contentDocument: ' + err
);
591 doc
= frame
.document
;
597 // Rails CSRF hack (thanks to Yvan Barthelemy)
598 var csrf_token
= $('meta[name=csrf-token]').attr('content');
599 var csrf_param
= $('meta[name=csrf-param]').attr('content');
601 if (csrf_param
&& csrf_token
) {
602 s
.extraData
= s
.extraData
|| {};
603 s
.extraData
[csrf_param
] = csrf_token
;
606 // take a breath so that pending repaints get some cpu time before the upload starts
607 function doSubmit() {
608 // make sure form attrs are set
609 var t
= $form
.attr2('target'),
610 a
= $form
.attr2('action'),
611 mp
= 'multipart/form-data',
612 et
= $form
.attr('enctype') || $form
.attr('encoding') || mp
;
614 // update form attrs in IE friendly way
615 form
.setAttribute('target', id
);
616 if (!method
|| /post/i.test(method
)) {
617 form
.setAttribute('method', 'POST');
620 form
.setAttribute('action', s
.url
);
623 // ie borks in some cases when setting encoding
624 if (!s
.skipEncodingOverride
&& (!method
|| /post/i.test(method
))) {
626 encoding
: 'multipart/form-data',
627 enctype
: 'multipart/form-data'
633 timeoutHandle
= setTimeout(function() {
634 timedOut
= true; cb(CLIENT_TIMEOUT_ABORT
);
638 // look for server aborts
639 function checkState() {
641 var state
= getDoc(io
).readyState
;
643 log('state = ' + state
);
644 if (state
&& state
.toLowerCase() === 'uninitialized') {
645 setTimeout(checkState
, 50);
649 log('Server abort: ', e
, ' (', e
.name
, ')');
650 cb(SERVER_ABORT
); // eslint-disable-line callback-return
652 clearTimeout(timeoutHandle
);
654 timeoutHandle
= undefined;
658 // add "extra" data to form if provided in options
659 var extraInputs
= [];
663 for (var n
in s
.extraData
) {
664 if (s
.extraData
.hasOwnProperty(n
)) {
665 // if using the $.param format that allows for multiple values with the same name
666 if ($.isPlainObject(s
.extraData
[n
]) && s
.extraData
[n
].hasOwnProperty('name') && s
.extraData
[n
].hasOwnProperty('value')) {
668 $('<input type="hidden" name="' + s
.extraData
[n
].name
+ '">', ownerDocument
).val(s
.extraData
[n
].value
)
672 $('<input type="hidden" name="' + n
+ '">', ownerDocument
).val(s
.extraData
[n
])
679 if (!s
.iframeTarget
) {
680 // add iframe to doc and submit the form
684 if (io
.attachEvent
) {
685 io
.attachEvent('onload', cb
);
687 io
.addEventListener('load', cb
, false);
690 setTimeout(checkState
, 15);
696 // just in case form has element with name/id of 'submit'
697 var submitFn
= document
.createElement('form').submit
;
699 submitFn
.apply(form
);
703 // reset attrs and remove "extra" input elements
704 form
.setAttribute('action', a
);
705 form
.setAttribute('enctype', et
); // #380
707 form
.setAttribute('target', t
);
709 $form
.removeAttr('target');
711 $(extraInputs
).remove();
718 setTimeout(doSubmit
, 10); // this lets dom updates render
721 var data
, doc
, domCheckCount
= 50, callbackProcessed
;
724 if (xhr
.aborted
|| callbackProcessed
) {
730 log('cannot access response document');
733 if (e
=== CLIENT_TIMEOUT_ABORT
&& xhr
) {
734 xhr
.abort('timeout');
735 deferred
.reject(xhr
, 'timeout');
739 } else if (e
=== SERVER_ABORT
&& xhr
) {
740 xhr
.abort('server abort');
741 deferred
.reject(xhr
, 'error', 'server abort');
746 if (!doc
|| doc
.location
.href
=== s
.iframeSrc
) {
747 // response not received yet
753 if (io
.detachEvent
) {
754 io
.detachEvent('onload', cb
);
756 io
.removeEventListener('load', cb
, false);
759 var status
= 'success', errMsg
;
766 var isXml
= s
.dataType
=== 'xml' || doc
.XMLDocument
|| $.isXMLDoc(doc
);
768 log('isXml=' + isXml
);
770 if (!isXml
&& window
.opera
&& (doc
.body
=== null || !doc
.body
.innerHTML
)) {
771 if (--domCheckCount
) {
772 // in some browsers (Opera) the iframe DOM is not always traversable when
773 // the onload callback fires, so we loop a bit to accommodate
774 log('requeing onLoad callback, DOM not available');
779 // let this fall through because server response could be an empty document
780 // log('Could not access iframe DOM after mutiple tries.');
781 // throw 'DOMException: not available';
784 // log('response detected');
785 var docRoot
= doc
.body
? doc
.body
: doc
.documentElement
;
787 xhr
.responseText
= docRoot
? docRoot
.innerHTML
: null;
788 xhr
.responseXML
= doc
.XMLDocument
? doc
.XMLDocument
: doc
;
792 xhr
.getResponseHeader = function(header
){
793 var headers
= {'content-type': s
.dataType
};
795 return headers
[header
.toLowerCase()];
797 // support for XHR 'status' & 'statusText' emulation :
799 xhr
.status
= Number(docRoot
.getAttribute('status')) || xhr
.status
;
800 xhr
.statusText
= docRoot
.getAttribute('statusText') || xhr
.statusText
;
803 var dt
= (s
.dataType
|| '').toLowerCase();
804 var scr
= /(json|script|text)/.test(dt
);
806 if (scr
|| s
.textarea
) {
807 // see if user embedded response in textarea
808 var ta
= doc
.getElementsByTagName('textarea')[0];
811 xhr
.responseText
= ta
.value
;
812 // support for XHR 'status' & 'statusText' emulation :
813 xhr
.status
= Number(ta
.getAttribute('status')) || xhr
.status
;
814 xhr
.statusText
= ta
.getAttribute('statusText') || xhr
.statusText
;
817 // account for browsers injecting pre around json response
818 var pre
= doc
.getElementsByTagName('pre')[0];
819 var b
= doc
.getElementsByTagName('body')[0];
822 xhr
.responseText
= pre
.textContent
? pre
.textContent
: pre
.innerText
;
824 xhr
.responseText
= b
.textContent
? b
.textContent
: b
.innerText
;
828 } else if (dt
=== 'xml' && !xhr
.responseXML
&& xhr
.responseText
) {
829 xhr
.responseXML
= toXml(xhr
.responseText
); // eslint-disable-line no-use-before-define
833 data
= httpData(xhr
, dt
, s
); // eslint-disable-line no-use-before-define
836 status
= 'parsererror';
837 xhr
.error
= errMsg
= (err
|| status
);
841 log('error caught: ', err
);
843 xhr
.error
= errMsg
= (err
|| status
);
847 log('upload aborted');
851 if (xhr
.status
) { // we've set xhr.status
852 status
= ((xhr
.status
>= 200 && xhr
.status
< 300) || xhr
.status
=== 304) ? 'success' : 'error';
855 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
856 if (status
=== 'success') {
858 s
.success
.call(s
.context
, data
, 'success', xhr
);
861 deferred
.resolve(xhr
.responseText
, 'success', xhr
);
864 $.event
.trigger('ajaxSuccess', [xhr
, s
]);
868 if (typeof errMsg
=== 'undefined') {
869 errMsg
= xhr
.statusText
;
872 s
.error
.call(s
.context
, xhr
, status
, errMsg
);
874 deferred
.reject(xhr
, 'error', errMsg
);
876 $.event
.trigger('ajaxError', [xhr
, s
, errMsg
]);
881 $.event
.trigger('ajaxComplete', [xhr
, s
]);
884 if (g
&& !--$.active
) {
885 $.event
.trigger('ajaxStop');
889 s
.complete
.call(s
.context
, xhr
, status
);
892 callbackProcessed
= true;
894 clearTimeout(timeoutHandle
);
898 setTimeout(function() {
899 if (!s
.iframeTarget
) {
901 } else { // adding else to clean up existing iframe response.
902 $io
.attr('src', s
.iframeSrc
);
904 xhr
.responseXML
= null;
908 var toXml
= $.parseXML
|| function(s
, doc
) { // use parseXML if available (jQuery 1.5+)
909 if (window
.ActiveXObject
) {
910 doc
= new ActiveXObject('Microsoft.XMLDOM');
915 doc
= (new DOMParser()).parseFromString(s
, 'text/xml');
918 return (doc
&& doc
.documentElement
&& doc
.documentElement
.nodeName
!== 'parsererror') ? doc
: null;
920 var parseJSON
= $.parseJSON
|| function(s
) {
921 /* jslint evil:true */
922 return window
['eval']('(' + s
+ ')'); // eslint-disable-line dot-notation
925 var httpData = function(xhr
, type
, s
) { // mostly lifted from jq1.4.4
927 var ct
= xhr
.getResponseHeader('content-type') || '',
928 xml
= ((type
=== 'xml' || !type
) && ct
.indexOf('xml') >= 0),
929 data
= xml
? xhr
.responseXML
: xhr
.responseText
;
931 if (xml
&& data
.documentElement
.nodeName
=== 'parsererror') {
933 $.error('parsererror');
936 if (s
&& s
.dataFilter
) {
937 data
= s
.dataFilter(data
, type
);
939 if (typeof data
=== 'string') {
940 if ((type
=== 'json' || !type
) && ct
.indexOf('json') >= 0) {
941 data
= parseJSON(data
);
942 } else if ((type
=== 'script' || !type
) && ct
.indexOf('javascript') >= 0) {
955 * ajaxForm() provides a mechanism for fully automating form submission.
957 * The advantages of using this method instead of ajaxSubmit() are:
959 * 1: This method will include coordinates for <input type="image"> elements (if the element
960 * is used to submit the form).
961 * 2. This method will include the submit element's name/value data (for the element that was
962 * used to submit the form).
963 * 3. This method binds the submit() method to the form for you.
965 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
966 * passes the options argument along after properly binding events for submit elements and
969 $.fn
.ajaxForm = function(options
, data
, dataType
, onSuccess
) {
970 if (typeof options
=== 'string' || (options
=== false && arguments
.length
> 0)) {
974 'dataType' : dataType
977 if (typeof onSuccess
=== 'function') {
978 options
.success
= onSuccess
;
982 options
= options
|| {};
983 options
.delegation
= options
.delegation
&& $.isFunction($.fn
.on
);
985 // in jQuery 1.3+ we can fix mistakes with the ready state
986 if (!options
.delegation
&& this.length
=== 0) {
987 var o
= {s
: this.selector
, c
: this.context
};
989 if (!$.isReady
&& o
.s
) {
990 log('DOM not ready, queuing ajaxForm');
992 $(o
.s
, o
.c
).ajaxForm(options
);
998 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
999 log('terminating; zero elements found by selector' + ($.isReady
? '' : ' (DOM not ready)'));
1004 if (options
.delegation
) {
1006 .off('submit.form-plugin', this.selector
, doAjaxSubmit
)
1007 .off('click.form-plugin', this.selector
, captureSubmittingElement
)
1008 .on('submit.form-plugin', this.selector
, options
, doAjaxSubmit
)
1009 .on('click.form-plugin', this.selector
, options
, captureSubmittingElement
);
1014 return this.ajaxFormUnbind()
1015 .on('submit.form-plugin', options
, doAjaxSubmit
)
1016 .on('click.form-plugin', options
, captureSubmittingElement
);
1019 // private event handlers
1020 function doAjaxSubmit(e
) {
1021 /* jshint validthis:true */
1022 var options
= e
.data
;
1024 if (!e
.isDefaultPrevented()) { // if event has been canceled, don't proceed
1026 $(e
.target
).closest('form').ajaxSubmit(options
); // #365
1030 function captureSubmittingElement(e
) {
1031 /* jshint validthis:true */
1032 var target
= e
.target
;
1033 var $el
= $(target
);
1035 if (!$el
.is('[type=submit],[type=image]')) {
1036 // is this a child element of the submit el? (ex: a span within a button)
1037 var t
= $el
.closest('[type=submit]');
1039 if (t
.length
=== 0) {
1045 var form
= target
.form
;
1049 if (target
.type
=== 'image') {
1050 if (typeof e
.offsetX
!== 'undefined') {
1051 form
.clk_x
= e
.offsetX
;
1052 form
.clk_y
= e
.offsetY
;
1054 } else if (typeof $.fn
.offset
=== 'function') {
1055 var offset
= $el
.offset();
1057 form
.clk_x
= e
.pageX
- offset
.left
;
1058 form
.clk_y
= e
.pageY
- offset
.top
;
1061 form
.clk_x
= e
.pageX
- target
.offsetLeft
;
1062 form
.clk_y
= e
.pageY
- target
.offsetTop
;
1066 setTimeout(function() {
1067 form
.clk
= form
.clk_x
= form
.clk_y
= null;
1072 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
1073 $.fn
.ajaxFormUnbind = function() {
1074 return this.off('submit.form-plugin click.form-plugin');
1078 * formToArray() gathers form element data into an array of objects that can
1079 * be passed to any of the following ajax functions: $.get, $.post, or load.
1080 * Each object in the array has both a 'name' and 'value' property. An example of
1081 * an array for a simple login form might be:
1083 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
1085 * It is this array that is passed to pre-submit callback functions provided to the
1086 * ajaxSubmit() and ajaxForm() methods.
1088 $.fn
.formToArray = function(semantic
, elements
, filtering
) {
1091 if (this.length
=== 0) {
1096 var formId
= this.attr('id');
1097 var els
= (semantic
|| typeof form
.elements
=== 'undefined') ? form
.getElementsByTagName('*') : form
.elements
;
1101 els
= $.makeArray(els
); // convert to standard array
1104 // #386; account for inputs outside the form which use the 'form' attribute
1105 // FinesseRus: in non-IE browsers outside fields are already included in form.elements.
1106 if (formId
&& (semantic
|| /(Edge|Trident)\//.test(navigator
.userAgent
))) {
1107 els2
= $(':input[form="' + formId
+ '"]').get(); // hat tip @thet
1109 els
= (els
|| []).concat(els2
);
1113 if (!els
|| !els
.length
) {
1117 if ($.isFunction(filtering
)) {
1118 els
= $.map(els
, filtering
);
1121 var i
, j
, n
, v
, el
, max
, jmax
;
1123 for (i
= 0, max
= els
.length
; i
< max
; i
++) {
1126 if (!n
|| el
.disabled
) {
1130 if (semantic
&& form
.clk
&& el
.type
=== 'image') {
1131 // handle image inputs on the fly when semantic == true
1132 if (form
.clk
=== el
) {
1133 a
.push({name
: n
, value
: $(el
).val(), type
: el
.type
});
1134 a
.push({name
: n
+ '.x', value
: form
.clk_x
}, {name
: n
+ '.y', value
: form
.clk_y
});
1139 v
= $.fieldValue(el
, true);
1140 if (v
&& v
.constructor === Array
) {
1144 for (j
= 0, jmax
= v
.length
; j
< jmax
; j
++) {
1145 a
.push({name
: n
, value
: v
[j
]});
1148 } else if (feature
.fileapi
&& el
.type
=== 'file') {
1153 var files
= el
.files
;
1156 for (j
= 0; j
< files
.length
; j
++) {
1157 a
.push({name
: n
, value
: files
[j
], type
: el
.type
});
1161 a
.push({name
: n
, value
: '', type
: el
.type
});
1164 } else if (v
!== null && typeof v
!== 'undefined') {
1168 a
.push({name
: n
, value
: v
, type
: el
.type
, required
: el
.required
});
1172 if (!semantic
&& form
.clk
) {
1173 // input type=='image' are not found in elements array! handle it here
1174 var $input
= $(form
.clk
), input
= $input
[0];
1178 if (n
&& !input
.disabled
&& input
.type
=== 'image') {
1179 a
.push({name
: n
, value
: $input
.val()});
1180 a
.push({name
: n
+ '.x', value
: form
.clk_x
}, {name
: n
+ '.y', value
: form
.clk_y
});
1188 * Serializes form data into a 'submittable' string. This method will return a string
1189 * in the format: name1=value1&name2=value2
1191 $.fn
.formSerialize = function(semantic
) {
1192 // hand off to jQuery.param for proper encoding
1193 return $.param(this.formToArray(semantic
));
1197 * Serializes all field elements in the jQuery object into a query string.
1198 * This method will return a string in the format: name1=value1&name2=value2
1200 $.fn
.fieldSerialize = function(successful
) {
1203 this.each(function() {
1210 var v
= $.fieldValue(this, successful
);
1212 if (v
&& v
.constructor === Array
) {
1213 for (var i
= 0, max
= v
.length
; i
< max
; i
++) {
1214 a
.push({name
: n
, value
: v
[i
]});
1217 } else if (v
!== null && typeof v
!== 'undefined') {
1218 a
.push({name
: this.name
, value
: v
});
1222 // hand off to jQuery.param for proper encoding
1227 * Returns the value(s) of the element in the matched set. For example, consider the following form:
1230 * <input name="A" type="text">
1231 * <input name="A" type="text">
1232 * <input name="B" type="checkbox" value="B1">
1233 * <input name="B" type="checkbox" value="B2">
1234 * <input name="C" type="radio" value="C1">
1235 * <input name="C" type="radio" value="C2">
1236 * </fieldset></form>
1238 * var v = $('input[type=text]').fieldValue();
1239 * // if no values are entered into the text inputs
1241 * // if values entered into the text inputs are 'foo' and 'bar'
1242 * v === ['foo','bar']
1244 * var v = $('input[type=checkbox]').fieldValue();
1245 * // if neither checkbox is checked
1247 * // if both checkboxes are checked
1248 * v === ['B1', 'B2']
1250 * var v = $('input[type=radio]').fieldValue();
1251 * // if neither radio is checked
1253 * // if first radio is checked
1256 * The successful argument controls whether or not the field element must be 'successful'
1257 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
1258 * The default value of the successful argument is true. If this value is false the value(s)
1259 * for each element is returned.
1261 * Note: This method *always* returns an array. If no valid value can be determined the
1262 * array will be empty, otherwise it will contain one or more values.
1264 $.fn
.fieldValue = function(successful
) {
1265 for (var val
= [], i
= 0, max
= this.length
; i
< max
; i
++) {
1267 var v
= $.fieldValue(el
, successful
);
1269 if (v
=== null || typeof v
=== 'undefined' || (v
.constructor === Array
&& !v
.length
)) {
1273 if (v
.constructor === Array
) {
1284 * Returns the value of the field element.
1286 $.fieldValue = function(el
, successful
) {
1287 var n
= el
.name
, t
= el
.type
, tag
= el
.tagName
.toLowerCase();
1289 if (typeof successful
=== 'undefined') {
1293 /* eslint-disable no-mixed-operators */
1294 if (successful
&& (!n
|| el
.disabled
|| t
=== 'reset' || t
=== 'button' ||
1295 (t
=== 'checkbox' || t
=== 'radio') && !el
.checked
||
1296 (t
=== 'submit' || t
=== 'image') && el
.form
&& el
.form
.clk
!== el
||
1297 tag
=== 'select' && el
.selectedIndex
=== -1)) {
1298 /* eslint-enable no-mixed-operators */
1302 if (tag
=== 'select') {
1303 var index
= el
.selectedIndex
;
1309 var a
= [], ops
= el
.options
;
1310 var one
= (t
=== 'select-one');
1311 var max
= (one
? index
+ 1 : ops
.length
);
1313 for (var i
= (one
? index
: 0); i
< max
; i
++) {
1316 if (op
.selected
&& !op
.disabled
) {
1319 if (!v
) { // extra pain for IE...
1320 v
= (op
.attributes
&& op
.attributes
.value
&& !(op
.attributes
.value
.specified
)) ? op
.text
: op
.value
;
1334 return $(el
).val().replace(rCRLF
, '\r\n');
1338 * Clears the form data. Takes the following actions on the form's input fields:
1339 * - input text fields will have their 'value' property set to the empty string
1340 * - select elements will have their 'selectedIndex' property set to -1
1341 * - checkbox and radio inputs will have their 'checked' property set to false
1342 * - inputs of type submit, button, reset, and hidden will *not* be effected
1343 * - button elements will *not* be effected
1345 $.fn
.clearForm = function(includeHidden
) {
1346 return this.each(function() {
1347 $('input,select,textarea', this).clearFields(includeHidden
);
1352 * Clears the selected form elements.
1354 $.fn
.clearFields
= $.fn
.clearInputs = function(includeHidden
) {
1355 var re
= /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
1357 return this.each(function() {
1358 var t
= this.type
, tag
= this.tagName
.toLowerCase();
1360 if (re
.test(t
) || tag
=== 'textarea') {
1363 } else if (t
=== 'checkbox' || t
=== 'radio') {
1364 this.checked
= false;
1366 } else if (tag
=== 'select') {
1367 this.selectedIndex
= -1;
1369 } else if (t
=== 'file') {
1370 if (/MSIE/.test(navigator
.userAgent
)) {
1371 $(this).replaceWith($(this).clone(true));
1376 } else if (includeHidden
) {
1377 // includeHidden can be the value true, or it can be a selector string
1378 // indicating a special test; for example:
1379 // $('#myForm').clearForm('.special:hidden')
1380 // the above would clean hidden inputs that have the class of 'special'
1381 if ((includeHidden
=== true && /hidden/.test(t
)) ||
1382 (typeof includeHidden
=== 'string' && $(this).is(includeHidden
))) {
1391 * Resets the form data or individual elements. Takes the following actions
1392 * on the selected tags:
1393 * - all fields within form elements will be reset to their original value
1394 * - input / textarea / select fields will be reset to their original value
1395 * - option / optgroup fields (for multi-selects) will defaulted individually
1396 * - non-multiple options will find the right select to default
1397 * - label elements will be searched against its 'for' attribute
1398 * - all others will be searched for appropriate children to default
1400 $.fn
.resetForm = function() {
1401 return this.each(function() {
1403 var tag
= this.tagName
.toLowerCase();
1407 this.checked
= this.defaultChecked
;
1411 this.value
= this.defaultValue
;
1417 var select
= el
.parents('select');
1419 if (select
.length
&& select
[0].multiple
) {
1420 if (tag
=== 'option') {
1421 this.selected
= this.defaultSelected
;
1423 el
.find('option').resetForm();
1432 el
.find('option').each(function(i
) { // eslint-disable-line consistent-return
1433 this.selected
= this.defaultSelected
;
1434 if (this.defaultSelected
&& !el
[0].multiple
) {
1435 el
[0].selectedIndex
= i
;
1444 var forEl
= $(el
.attr('for'));
1445 var list
= el
.find('input,select,textarea');
1448 list
.unshift(forEl
[0]);
1456 // guard against an input with the name of 'reset'
1457 // note that IE reports the reset function as an 'object'
1458 if (typeof this.reset
=== 'function' || (typeof this.reset
=== 'object' && !this.reset
.nodeType
)) {
1465 el
.find('form,input,label,select,textarea').resetForm();
1473 * Enables or disables any matching elements.
1475 $.fn
.enable = function(b
) {
1476 if (typeof b
=== 'undefined') {
1480 return this.each(function() {
1486 * Checks/unchecks any matching checkboxes or radio buttons and
1487 * selects/deselects and matching option elements.
1489 $.fn
.selected = function(select
) {
1490 if (typeof select
=== 'undefined') {
1494 return this.each(function() {
1497 if (t
=== 'checkbox' || t
=== 'radio') {
1498 this.checked
= select
;
1500 } else if (this.tagName
.toLowerCase() === 'option') {
1501 var $sel
= $(this).parent('select');
1503 if (select
&& $sel
[0] && $sel
[0].type
=== 'select-one') {
1504 // deselect all other options
1505 $sel
.find('option').selected(false);
1508 this.selected
= select
;
1514 $.fn
.ajaxSubmit
.debug
= false;
1516 // helper fn for console logging
1518 if (!$.fn
.ajaxSubmit
.debug
) {
1522 var msg
= '[jquery.form] ' + Array
.prototype.join
.call(arguments
, '');
1524 if (window
.console
&& window
.console
.log
) {
1525 window
.console
.log(msg
);
1527 } else if (window
.opera
&& window
.opera
.postError
) {
1528 window
.opera
.postError(msg
);