3 * version: 2.87 (20-OCT-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 var method, action, url, $form = this;
55 if (typeof options == 'function') {
56 options = { success: options };
59 method = this.attr('method');
60 action = this.attr('action');
61 url = (typeof action === 'string') ? $.trim(action) : '';
62 url = url || window.location.href || '';
64 // clean url (don't include hash vaue)
65 url = (url.match(/^([^#]+)/)||[])[1];
68 options = $.extend(true, {
70 success: $.ajaxSettings.success,
71 type: method || 'GET',
72 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
75 // hook for manipulating the form data before it is extracted;
76 // convenient for use with rich editors like tinyMCE or FCKEditor
78 this.trigger('form-pre-serialize', [this, options, veto]);
80 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
84 // provide opportunity to alter form data before it is serialized
85 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
86 log('ajaxSubmit: submit aborted via beforeSerialize callback');
90 var traditional = options.traditional;
91 if ( traditional === undefined ) {
92 traditional = $.ajaxSettings.traditional;
95 var qx,n,v,a = this.formToArray(options.semantic);
97 options.extraData = options.data;
98 qx = $.param(options.data, traditional);
101 // give pre-submit callback an opportunity to abort the submit
102 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
103 log('ajaxSubmit: submit aborted via beforeSubmit callback');
107 // fire vetoable 'validate' event
108 this.trigger('form-submit-validate', [a, this, options, veto]);
110 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
114 var q = $.param(a, traditional);
116 q = ( q ? (q + '&' + qx) : qx );
118 if (options.type.toUpperCase() == 'GET') {
119 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
120 options.data = null; // data is null for 'get'
123 options.data = q; // data is the query string for 'post'
127 if (options.resetForm) {
128 callbacks.push(function() { $form.resetForm(); });
130 if (options.clearForm) {
131 callbacks.push(function() { $form.clearForm(options.includeHidden); });
134 // perform a load on the target only if dataType is not provided
135 if (!options.dataType && options.target) {
136 var oldSuccess = options.success || function(){};
137 callbacks.push(function(data) {
138 var fn = options.replaceTarget ? 'replaceWith' : 'html';
139 $(options.target)[fn](data).each(oldSuccess, arguments);
142 else if (options.success) {
143 callbacks.push(options.success);
146 options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
147 var context = options.context || options; // jQuery 1.4+ supports scope context
148 for (var i=0, max=callbacks.length; i < max; i++) {
149 callbacks[i].apply(context, [data, status, xhr || $form, $form]);
153 // are there files to upload?
154 var fileInputs = $('input:file', this).length > 0;
155 var mp = 'multipart/form-data';
156 var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
158 // options.iframe allows user to force iframe mode
159 // 06-NOV-09: now defaulting to iframe mode if file input is detected
160 if (options.iframe !== false && (fileInputs || options.iframe || multipart)) {
161 // hack to fix Safari hang (thanks to Tim Molendijk for this)
162 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
163 if (options.closeKeepAlive) {
164 $.get(options.closeKeepAlive, function() { fileUpload(a); });
171 // IE7 massage (see issue 57)
172 if ($.browser.msie && method == 'get' && typeof options.type === "undefined") {
173 var ieMeth = $form[0].getAttribute('method');
174 if (typeof ieMeth === 'string')
175 options.type = ieMeth;
180 // fire 'notify' event
181 this.trigger('form-submit-notify', [this, options]);
185 // private function for handling file uploads (hat tip to YAHOO!)
186 function fileUpload(a) {
187 var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
188 var useProp = !!$.fn.prop;
192 // ensure that every serialized input is still enabled
193 for (i=0; i < a.length; i++) {
194 el = $(form[a[i].name]);
195 el.prop('disabled', false);
198 for (i=0; i < a.length; i++) {
199 el = $(form[a[i].name]);
200 el.removeAttr('disabled');
205 if ($(':input[name=submit],:input[id=submit]', form).length) {
206 // if there is an input with a name or id of 'submit' then we won't be
207 // able to invoke the submit fn on the form (at least not x-browser)
208 alert('Error: Form elements must not have name or id of "submit".');
212 s = $.extend(true, {}, $.ajaxSettings, options);
213 s.context = s.context || s;
214 id = 'jqFormIO' + (new Date().getTime());
215 if (s.iframeTarget) {
216 $io = $(s.iframeTarget);
217 n = $io.attr('name');
219 $io.attr('name', id);
224 $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
225 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
230 xhr = { // mock object
236 getAllResponseHeaders: function() {},
237 getResponseHeader: function() {},
238 setRequestHeader: function() {},
239 abort: function(status) {
240 var e = (status === 'timeout' ? 'timeout' : 'aborted');
241 log('aborting upload... ' + e);
243 $io.attr('src', s.iframeSrc); // abort op in progress
245 s.error && s.error.call(s.context, xhr, e, status);
246 g && $.event.trigger("ajaxError", [xhr, s, e]);
247 s.complete && s.complete.call(s.context, xhr, e);
252 // trigger ajax global events so that activity/block indicators work like normal
253 if (g && ! $.active++) {
254 $.event.trigger("ajaxStart");
257 $.event.trigger("ajaxSend", [xhr, s]);
260 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
270 // add submitting element to data if we know it
274 if (n && !sub.disabled) {
275 s.extraData = s.extraData || {};
276 s.extraData[n] = sub.value;
277 if (sub.type == "image") {
278 s.extraData[n+'.x'] = form.clk_x;
279 s.extraData[n+'.y'] = form.clk_y;
284 var CLIENT_TIMEOUT_ABORT = 1;
285 var SERVER_ABORT = 2;
287 function getDoc(frame) {
288 var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
292 // take a breath so that pending repaints get some cpu time before the upload starts
293 function doSubmit() {
294 // make sure form attrs are set
295 var t = $form.attr('target'), a = $form.attr('action');
297 // update form attrs in IE friendly way
298 form.setAttribute('target',id);
300 form.setAttribute('method', 'POST');
303 form.setAttribute('action', s.url);
306 // ie borks in some cases when setting encoding
307 if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
309 encoding: 'multipart/form-data',
310 enctype: 'multipart/form-data'
316 timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
319 // look for server aborts
320 function checkState() {
322 var state = getDoc(io).readyState;
323 log('state = ' + state);
324 if (state.toLowerCase() == 'uninitialized')
325 setTimeout(checkState,50);
328 log('Server abort: ' , e, ' (', e.name, ')');
330 timeoutHandle && clearTimeout(timeoutHandle);
331 timeoutHandle = undefined;
335 // add "extra" data to form if provided in options
336 var extraInputs = [];
339 for (var n in s.extraData) {
341 $('<input type="hidden" name="'+n+'" />').attr('value',s.extraData[n])
346 if (!s.iframeTarget) {
347 // add iframe to doc and submit the form
348 $io.appendTo('body');
349 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
351 setTimeout(checkState,15);
355 // reset attrs and remove "extra" input elements
356 form.setAttribute('action',a);
358 form.setAttribute('target', t);
360 $form.removeAttr('target');
362 $(extraInputs).remove();
370 setTimeout(doSubmit, 10); // this lets dom updates render
373 var data, doc, domCheckCount = 50, callbackProcessed;
376 if (xhr.aborted || callbackProcessed) {
383 log('cannot access response document: ', ex);
386 if (e === CLIENT_TIMEOUT_ABORT && xhr) {
387 xhr.abort('timeout');
390 else if (e == SERVER_ABORT && xhr) {
391 xhr.abort('server abort');
395 if (!doc || doc.location.href == s.iframeSrc) {
396 // response not received yet
400 io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
402 var status = 'success', errMsg;
408 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
410 if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
411 if (--domCheckCount) {
412 // in some browsers (Opera) the iframe DOM is not always traversable when
413 // the onload callback fires, so we loop a bit to accommodate
414 log('requeing onLoad callback, DOM not available');
418 // let this fall through because server response could be an empty document
419 //log('Could not access iframe DOM after mutiple tries.');
420 //throw 'DOMException: not available';
423 //log('response detected');
424 var docRoot = doc.body ? doc.body : doc.documentElement;
425 xhr.responseText = docRoot ? docRoot.innerHTML : null;
426 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
429 xhr.getResponseHeader = function(header){
430 var headers = {'content-type': s.dataType};
431 return headers[header];
433 // support for XHR 'status' & 'statusText' emulation :
435 xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
436 xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
439 var dt = (s.dataType || '').toLowerCase();
440 var scr = /(json|script|text)/.test(dt);
441 if (scr || s.textarea) {
442 // see if user embedded response in textarea
443 var ta = doc.getElementsByTagName('textarea')[0];
445 xhr.responseText = ta.value;
446 // support for XHR 'status' & 'statusText' emulation :
447 xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
448 xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
451 // account for browsers injecting pre around json response
452 var pre = doc.getElementsByTagName('pre')[0];
453 var b = doc.getElementsByTagName('body')[0];
455 xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
458 xhr.responseText = b.textContent ? b.textContent : b.innerText;
462 else if (dt == 'xml' && !xhr.responseXML && xhr.responseText != null) {
463 xhr.responseXML = toXml(xhr.responseText);
467 data = httpData(xhr, dt, s);
470 status = 'parsererror';
471 xhr.error = errMsg = (e || status);
475 log('error caught: ',e);
477 xhr.error = errMsg = (e || status);
481 log('upload aborted');
485 if (xhr.status) { // we've set xhr.status
486 status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
489 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
490 if (status === 'success') {
491 s.success && s.success.call(s.context, data, 'success', xhr);
492 g && $.event.trigger("ajaxSuccess", [xhr, s]);
495 if (errMsg == undefined)
496 errMsg = xhr.statusText;
497 s.error && s.error.call(s.context, xhr, status, errMsg);
498 g && $.event.trigger("ajaxError", [xhr, s, errMsg]);
501 g && $.event.trigger("ajaxComplete", [xhr, s]);
503 if (g && ! --$.active) {
504 $.event.trigger("ajaxStop");
507 s.complete && s.complete.call(s.context, xhr, status);
509 callbackProcessed = true;
511 clearTimeout(timeoutHandle);
514 setTimeout(function() {
517 xhr.responseXML = null;
521 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
522 if (window.ActiveXObject) {
523 doc = new ActiveXObject('Microsoft.XMLDOM');
528 doc = (new DOMParser()).parseFromString(s, 'text/xml');
530 return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
532 var parseJSON = $.parseJSON || function(s) {
533 return window['eval']('(' + s + ')');
536 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
538 var ct = xhr.getResponseHeader('content-type') || '',
539 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
540 data = xml ? xhr.responseXML : xhr.responseText;
542 if (xml && data.documentElement.nodeName === 'parsererror') {
543 $.error && $.error('parsererror');
545 if (s && s.dataFilter) {
546 data = s.dataFilter(data, type);
548 if (typeof data === 'string') {
549 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
550 data = parseJSON(data);
551 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
561 * ajaxForm() provides a mechanism for fully automating form submission.
563 * The advantages of using this method instead of ajaxSubmit() are:
565 * 1: This method will include coordinates for <input type="image" /> elements (if the element
566 * is used to submit the form).
567 * 2. This method will include the submit element's name/value data (for the element that was
568 * used to submit the form).
569 * 3. This method binds the submit() method to the form for you.
571 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
572 * passes the options argument along after properly binding events for submit elements and
575 $.fn.ajaxForm = function(options) {
576 // in jQuery 1.3+ we can fix mistakes with the ready state
577 if (this.length === 0) {
578 var o = { s: this.selector, c: this.context };
579 if (!$.isReady && o.s) {
580 log('DOM not ready, queuing ajaxForm');
582 $(o.s,o.c).ajaxForm(options);
586 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
587 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
591 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
592 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
594 $(this).ajaxSubmit(options);
596 }).bind('click.form-plugin', function(e) {
597 var target = e.target;
599 if (!($el.is(":submit,input:image"))) {
600 // is this a child element of the submit el? (ex: a span within a button)
601 var t = $el.closest(':submit');
609 if (target.type == 'image') {
610 if (e.offsetX != undefined) {
611 form.clk_x = e.offsetX;
612 form.clk_y = e.offsetY;
613 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
614 var offset = $el.offset();
615 form.clk_x = e.pageX - offset.left;
616 form.clk_y = e.pageY - offset.top;
618 form.clk_x = e.pageX - target.offsetLeft;
619 form.clk_y = e.pageY - target.offsetTop;
623 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
627 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
628 $.fn.ajaxFormUnbind = function() {
629 return this.unbind('submit.form-plugin click.form-plugin');
633 * formToArray() gathers form element data into an array of objects that can
634 * be passed to any of the following ajax functions: $.get, $.post, or load.
635 * Each object in the array has both a 'name' and 'value' property. An example of
636 * an array for a simple login form might be:
638 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
640 * It is this array that is passed to pre-submit callback functions provided to the
641 * ajaxSubmit() and ajaxForm() methods.
643 $.fn.formToArray = function(semantic) {
645 if (this.length === 0) {
650 var els = semantic ? form.getElementsByTagName('*') : form.elements;
655 var i,j,n,v,el,max,jmax;
656 for(i=0, max=els.length; i < max; i++) {
663 if (semantic && form.clk && el.type == "image") {
664 // handle image inputs on the fly when semantic == true
665 if(!el.disabled && form.clk == el) {
666 a.push({name: n, value: $(el).val()});
667 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
672 v = $.fieldValue(el, true);
673 if (v && v.constructor == Array) {
674 for(j=0, jmax=v.length; j < jmax; j++) {
675 a.push({name: n, value: v[j]});
678 else if (v !== null && typeof v != 'undefined') {
679 a.push({name: n, value: v});
683 if (!semantic && form.clk) {
684 // input type=='image' are not found in elements array! handle it here
685 var $input = $(form.clk), input = $input[0];
687 if (n && !input.disabled && input.type == 'image') {
688 a.push({name: n, value: $input.val()});
689 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
696 * Serializes form data into a 'submittable' string. This method will return a string
697 * in the format: name1=value1&name2=value2
699 $.fn.formSerialize = function(semantic) {
700 //hand off to jQuery.param for proper encoding
701 return $.param(this.formToArray(semantic));
705 * Serializes all field elements in the jQuery object into a query string.
706 * This method will return a string in the format: name1=value1&name2=value2
708 $.fn.fieldSerialize = function(successful) {
710 this.each(function() {
715 var v = $.fieldValue(this, successful);
716 if (v && v.constructor == Array) {
717 for (var i=0,max=v.length; i < max; i++) {
718 a.push({name: n, value: v[i]});
721 else if (v !== null && typeof v != 'undefined') {
722 a.push({name: this.name, value: v});
725 //hand off to jQuery.param for proper encoding
730 * Returns the value(s) of the element in the matched set. For example, consider the following form:
733 * <input name="A" type="text" />
734 * <input name="A" type="text" />
735 * <input name="B" type="checkbox" value="B1" />
736 * <input name="B" type="checkbox" value="B2"/>
737 * <input name="C" type="radio" value="C1" />
738 * <input name="C" type="radio" value="C2" />
741 * var v = $(':text').fieldValue();
742 * // if no values are entered into the text inputs
744 * // if values entered into the text inputs are 'foo' and 'bar'
747 * var v = $(':checkbox').fieldValue();
748 * // if neither checkbox is checked
750 * // if both checkboxes are checked
753 * var v = $(':radio').fieldValue();
754 * // if neither radio is checked
756 * // if first radio is checked
759 * The successful argument controls whether or not the field element must be 'successful'
760 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
761 * The default value of the successful argument is true. If this value is false the value(s)
762 * for each element is returned.
764 * Note: This method *always* returns an array. If no valid value can be determined the
765 * array will be empty, otherwise it will contain one or more values.
767 $.fn.fieldValue = function(successful) {
768 for (var val=[], i=0, max=this.length; i < max; i++) {
770 var v = $.fieldValue(el, successful);
771 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
774 v.constructor == Array ? $.merge(val, v) : val.push(v);
780 * Returns the value of the field element.
782 $.fieldValue = function(el, successful) {
783 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
784 if (successful === undefined) {
788 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
789 (t == 'checkbox' || t == 'radio') && !el.checked ||
790 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
791 tag == 'select' && el.selectedIndex == -1)) {
795 if (tag == 'select') {
796 var index = el.selectedIndex;
800 var a = [], ops = el.options;
801 var one = (t == 'select-one');
802 var max = (one ? index+1 : ops.length);
803 for(var i=(one ? index : 0); i < max; i++) {
807 if (!v) { // extra pain for IE...
808 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
822 * Clears the form data. Takes the following actions on the form's input fields:
823 * - input text fields will have their 'value' property set to the empty string
824 * - select elements will have their 'selectedIndex' property set to -1
825 * - checkbox and radio inputs will have their 'checked' property set to false
826 * - inputs of type submit, button, reset, and hidden will *not* be effected
827 * - button elements will *not* be effected
829 $.fn.clearForm = function(includeHidden) {
830 return this.each(function() {
831 $('input,select,textarea', this).clearFields(includeHidden);
836 * Clears the selected form elements.
838 $.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
839 var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
840 return this.each(function() {
841 var t = this.type, tag = this.tagName.toLowerCase();
842 if (re.test(t) || tag == 'textarea' || (includeHidden && /hidden/.test(t)) ) {
845 else if (t == 'checkbox' || t == 'radio') {
846 this.checked = false;
848 else if (tag == 'select') {
849 this.selectedIndex = -1;
855 * Resets the form data. Causes all form elements to be reset to their original value.
857 $.fn.resetForm = function() {
858 return this.each(function() {
859 // guard against an input with the name of 'reset'
860 // note that IE reports the reset function as an 'object'
861 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
868 * Enables or disables any matching elements.
870 $.fn.enable = function(b) {
871 if (b === undefined) {
874 return this.each(function() {
880 * Checks/unchecks any matching checkboxes or radio buttons and
881 * selects/deselects and matching option elements.
883 $.fn.selected = function(select) {
884 if (select === undefined) {
887 return this.each(function() {
889 if (t == 'checkbox' || t == 'radio') {
890 this.checked = select;
892 else if (this.tagName.toLowerCase() == 'option') {
893 var $sel = $(this).parent('select');
894 if (select && $sel[0] && $sel[0].type == 'select-one') {
895 // deselect all other options
896 $sel.find('option').selected(false);
898 this.selected = select;
904 $.fn.ajaxSubmit.debug = false;
906 // helper fn for console logging
908 if (!$.fn.ajaxSubmit.debug)
910 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
911 if (window.console && window.console.log) {
912 window.console.log(msg);
914 else if (window.opera && window.opera.postError) {
915 window.opera.postError(msg);