3 * version: 2.84 (12-AUG-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 n,v,a = this.formToArray(options.semantic);
92 options.extraData = options.data;
93 for (n in options.data) {
94 if( $.isArray(options.data[n]) ) {
95 for (var k in options.data[n]) {
96 a.push( { name: n, value: options.data[n][k] } );
101 v = $.isFunction(v) ? v() : v; // if value is fn, invoke it
102 a.push( { name: n, value: v } );
107 // give pre-submit callback an opportunity to abort the submit
108 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
109 log('ajaxSubmit: submit aborted via beforeSubmit callback');
113 // fire vetoable 'validate' event
114 this.trigger('form-submit-validate', [a, this, options, veto]);
116 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
122 if (options.type.toUpperCase() == 'GET') {
123 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
124 options.data = null; // data is null for 'get'
127 options.data = q; // data is the query string for 'post'
131 if (options.resetForm) {
132 callbacks.push(function() { $form.resetForm(); });
134 if (options.clearForm) {
135 callbacks.push(function() { $form.clearForm(); });
138 // perform a load on the target only if dataType is not provided
139 if (!options.dataType && options.target) {
140 var oldSuccess = options.success || function(){};
141 callbacks.push(function(data) {
142 var fn = options.replaceTarget ? 'replaceWith' : 'html';
143 $(options.target)[fn](data).each(oldSuccess, arguments);
146 else if (options.success) {
147 callbacks.push(options.success);
150 options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
151 var context = options.context || options; // jQuery 1.4+ supports scope context
152 for (var i=0, max=callbacks.length; i < max; i++) {
153 callbacks[i].apply(context, [data, status, xhr || $form, $form]);
157 // are there files to upload?
158 var fileInputs = $('input:file', this).length > 0;
159 var mp = 'multipart/form-data';
160 var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
162 // options.iframe allows user to force iframe mode
163 // 06-NOV-09: now defaulting to iframe mode if file input is detected
164 if (options.iframe !== false && (fileInputs || options.iframe || multipart)) {
165 // hack to fix Safari hang (thanks to Tim Molendijk for this)
166 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
167 if (options.closeKeepAlive) {
168 $.get(options.closeKeepAlive, function() { fileUpload(a); });
175 // IE7 massage (see issue 57)
176 if ($.browser.msie && method == 'get') {
177 var ieMeth = $form[0].getAttribute('method');
178 if (typeof ieMeth === 'string')
179 options.type = ieMeth;
184 // fire 'notify' event
185 this.trigger('form-submit-notify', [this, options]);
189 // private function for handling file uploads (hat tip to YAHOO!)
190 function fileUpload(a) {
191 var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
192 var useProp = !!$.fn.prop;
195 // ensure that every serialized input is still enabled
196 for (i=0; i < a.length; i++) {
197 el = $(form[a[i].name]);
198 el[ useProp ? 'prop' : 'attr' ]('disabled', false);
202 if ($(':input[name=submit],:input[id=submit]', form).length) {
203 // if there is an input with a name or id of 'submit' then we won't be
204 // able to invoke the submit fn on the form (at least not x-browser)
205 alert('Error: Form elements must not have name or id of "submit".');
209 s = $.extend(true, {}, $.ajaxSettings, options);
210 s.context = s.context || s;
211 id = 'jqFormIO' + (new Date().getTime());
212 if (s.iframeTarget) {
213 $io = $(s.iframeTarget);
214 n = $io.attr('name');
216 $io.attr('name', id);
221 $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
222 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
227 xhr = { // mock object
233 getAllResponseHeaders: function() {},
234 getResponseHeader: function() {},
235 setRequestHeader: function() {},
236 abort: function(status) {
237 var e = (status === 'timeout' ? 'timeout' : 'aborted');
238 log('aborting upload... ' + e);
240 $io.attr('src', s.iframeSrc); // abort op in progress
242 s.error && s.error.call(s.context, xhr, e, status);
243 g && $.event.trigger("ajaxError", [xhr, s, e]);
244 s.complete && s.complete.call(s.context, xhr, e);
249 // trigger ajax global events so that activity/block indicators work like normal
250 if (g && ! $.active++) {
251 $.event.trigger("ajaxStart");
254 $.event.trigger("ajaxSend", [xhr, s]);
257 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
267 // add submitting element to data if we know it
271 if (n && !sub.disabled) {
272 s.extraData = s.extraData || {};
273 s.extraData[n] = sub.value;
274 if (sub.type == "image") {
275 s.extraData[n+'.x'] = form.clk_x;
276 s.extraData[n+'.y'] = form.clk_y;
281 var CLIENT_TIMEOUT_ABORT = 1;
282 var SERVER_ABORT = 2;
284 function getDoc(frame) {
285 var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
289 // take a breath so that pending repaints get some cpu time before the upload starts
290 function doSubmit() {
291 // make sure form attrs are set
292 var t = $form.attr('target'), a = $form.attr('action');
294 // update form attrs in IE friendly way
295 form.setAttribute('target',id);
297 form.setAttribute('method', 'POST');
300 form.setAttribute('action', s.url);
303 // ie borks in some cases when setting encoding
304 if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
306 encoding: 'multipart/form-data',
307 enctype: 'multipart/form-data'
313 timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
316 // look for server aborts
317 function checkState() {
319 var state = getDoc(io).readyState;
320 log('state = ' + state);
321 if (state.toLowerCase() == 'uninitialized')
322 setTimeout(checkState,50);
325 log('Server abort: ' , e, ' (', e.name, ')');
327 timeoutHandle && clearTimeout(timeoutHandle);
328 timeoutHandle = undefined;
332 // add "extra" data to form if provided in options
333 var extraInputs = [];
336 for (var n in s.extraData) {
338 $('<input type="hidden" name="'+n+'" />').attr('value',s.extraData[n])
343 if (!s.iframeTarget) {
344 // add iframe to doc and submit the form
345 $io.appendTo('body');
346 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
348 setTimeout(checkState,15);
352 // reset attrs and remove "extra" input elements
353 form.setAttribute('action',a);
355 form.setAttribute('target', t);
357 $form.removeAttr('target');
359 $(extraInputs).remove();
367 setTimeout(doSubmit, 10); // this lets dom updates render
370 var data, doc, domCheckCount = 50, callbackProcessed;
373 if (xhr.aborted || callbackProcessed) {
380 log('cannot access response document: ', ex);
383 if (e === CLIENT_TIMEOUT_ABORT && xhr) {
384 xhr.abort('timeout');
387 else if (e == SERVER_ABORT && xhr) {
388 xhr.abort('server abort');
392 if (!doc || doc.location.href == s.iframeSrc) {
393 // response not received yet
397 io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
399 var status = 'success', errMsg;
405 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
407 if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
408 if (--domCheckCount) {
409 // in some browsers (Opera) the iframe DOM is not always traversable when
410 // the onload callback fires, so we loop a bit to accommodate
411 log('requeing onLoad callback, DOM not available');
415 // let this fall through because server response could be an empty document
416 //log('Could not access iframe DOM after mutiple tries.');
417 //throw 'DOMException: not available';
420 //log('response detected');
421 var docRoot = doc.body ? doc.body : doc.documentElement;
422 xhr.responseText = docRoot ? docRoot.innerHTML : null;
423 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
426 xhr.getResponseHeader = function(header){
427 var headers = {'content-type': s.dataType};
428 return headers[header];
430 // support for XHR 'status' & 'statusText' emulation :
432 xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
433 xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
436 var dt = s.dataType || '';
437 var scr = /(json|script|text)/.test(dt.toLowerCase());
438 if (scr || s.textarea) {
439 // see if user embedded response in textarea
440 var ta = doc.getElementsByTagName('textarea')[0];
442 xhr.responseText = ta.value;
443 // support for XHR 'status' & 'statusText' emulation :
444 xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
445 xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
448 // account for browsers injecting pre around json response
449 var pre = doc.getElementsByTagName('pre')[0];
450 var b = doc.getElementsByTagName('body')[0];
452 xhr.responseText = pre.textContent ? pre.textContent : pre.innerHTML;
455 xhr.responseText = b.innerHTML;
459 else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
460 xhr.responseXML = toXml(xhr.responseText);
464 data = httpData(xhr, s.dataType, s);
467 status = 'parsererror';
468 xhr.error = errMsg = (e || status);
472 log('error caught: ',e);
474 xhr.error = errMsg = (e || status);
478 log('upload aborted');
482 if (xhr.status) { // we've set xhr.status
483 status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
486 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
487 if (status === 'success') {
488 s.success && s.success.call(s.context, data, 'success', xhr);
489 g && $.event.trigger("ajaxSuccess", [xhr, s]);
492 if (errMsg == undefined)
493 errMsg = xhr.statusText;
494 s.error && s.error.call(s.context, xhr, status, errMsg);
495 g && $.event.trigger("ajaxError", [xhr, s, errMsg]);
498 g && $.event.trigger("ajaxComplete", [xhr, s]);
500 if (g && ! --$.active) {
501 $.event.trigger("ajaxStop");
504 s.complete && s.complete.call(s.context, xhr, status);
506 callbackProcessed = true;
508 clearTimeout(timeoutHandle);
511 setTimeout(function() {
514 xhr.responseXML = null;
518 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
519 if (window.ActiveXObject) {
520 doc = new ActiveXObject('Microsoft.XMLDOM');
525 doc = (new DOMParser()).parseFromString(s, 'text/xml');
527 return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
529 var parseJSON = $.parseJSON || function(s) {
530 return window['eval']('(' + s + ')');
533 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
535 var ct = xhr.getResponseHeader('content-type') || '',
536 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
537 data = xml ? xhr.responseXML : xhr.responseText;
539 if (xml && data.documentElement.nodeName === 'parsererror') {
540 $.error && $.error('parsererror');
542 if (s && s.dataFilter) {
543 data = s.dataFilter(data, type);
545 if (typeof data === 'string') {
546 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
547 data = parseJSON(data);
548 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
558 * ajaxForm() provides a mechanism for fully automating form submission.
560 * The advantages of using this method instead of ajaxSubmit() are:
562 * 1: This method will include coordinates for <input type="image" /> elements (if the element
563 * is used to submit the form).
564 * 2. This method will include the submit element's name/value data (for the element that was
565 * used to submit the form).
566 * 3. This method binds the submit() method to the form for you.
568 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
569 * passes the options argument along after properly binding events for submit elements and
572 $.fn.ajaxForm = function(options) {
573 // in jQuery 1.3+ we can fix mistakes with the ready state
574 if (this.length === 0) {
575 var o = { s: this.selector, c: this.context };
576 if (!$.isReady && o.s) {
577 log('DOM not ready, queuing ajaxForm');
579 $(o.s,o.c).ajaxForm(options);
583 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
584 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
588 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
589 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
591 $(this).ajaxSubmit(options);
593 }).bind('click.form-plugin', function(e) {
594 var target = e.target;
596 if (!($el.is(":submit,input:image"))) {
597 // is this a child element of the submit el? (ex: a span within a button)
598 var t = $el.closest(':submit');
606 if (target.type == 'image') {
607 if (e.offsetX != undefined) {
608 form.clk_x = e.offsetX;
609 form.clk_y = e.offsetY;
610 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
611 var offset = $el.offset();
612 form.clk_x = e.pageX - offset.left;
613 form.clk_y = e.pageY - offset.top;
615 form.clk_x = e.pageX - target.offsetLeft;
616 form.clk_y = e.pageY - target.offsetTop;
620 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
624 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
625 $.fn.ajaxFormUnbind = function() {
626 return this.unbind('submit.form-plugin click.form-plugin');
630 * formToArray() gathers form element data into an array of objects that can
631 * be passed to any of the following ajax functions: $.get, $.post, or load.
632 * Each object in the array has both a 'name' and 'value' property. An example of
633 * an array for a simple login form might be:
635 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
637 * It is this array that is passed to pre-submit callback functions provided to the
638 * ajaxSubmit() and ajaxForm() methods.
640 $.fn.formToArray = function(semantic) {
642 if (this.length === 0) {
647 var els = semantic ? form.getElementsByTagName('*') : form.elements;
652 var i,j,n,v,el,max,jmax;
653 for(i=0, max=els.length; i < max; i++) {
660 if (semantic && form.clk && el.type == "image") {
661 // handle image inputs on the fly when semantic == true
662 if(!el.disabled && form.clk == el) {
663 a.push({name: n, value: $(el).val()});
664 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
669 v = $.fieldValue(el, true);
670 if (v && v.constructor == Array) {
671 for(j=0, jmax=v.length; j < jmax; j++) {
672 a.push({name: n, value: v[j]});
675 else if (v !== null && typeof v != 'undefined') {
676 a.push({name: n, value: v});
680 if (!semantic && form.clk) {
681 // input type=='image' are not found in elements array! handle it here
682 var $input = $(form.clk), input = $input[0];
684 if (n && !input.disabled && input.type == 'image') {
685 a.push({name: n, value: $input.val()});
686 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
693 * Serializes form data into a 'submittable' string. This method will return a string
694 * in the format: name1=value1&name2=value2
696 $.fn.formSerialize = function(semantic) {
697 //hand off to jQuery.param for proper encoding
698 return $.param(this.formToArray(semantic));
702 * Serializes all field elements in the jQuery object into a query string.
703 * This method will return a string in the format: name1=value1&name2=value2
705 $.fn.fieldSerialize = function(successful) {
707 this.each(function() {
712 var v = $.fieldValue(this, successful);
713 if (v && v.constructor == Array) {
714 for (var i=0,max=v.length; i < max; i++) {
715 a.push({name: n, value: v[i]});
718 else if (v !== null && typeof v != 'undefined') {
719 a.push({name: this.name, value: v});
722 //hand off to jQuery.param for proper encoding
727 * Returns the value(s) of the element in the matched set. For example, consider the following form:
730 * <input name="A" type="text" />
731 * <input name="A" type="text" />
732 * <input name="B" type="checkbox" value="B1" />
733 * <input name="B" type="checkbox" value="B2"/>
734 * <input name="C" type="radio" value="C1" />
735 * <input name="C" type="radio" value="C2" />
738 * var v = $(':text').fieldValue();
739 * // if no values are entered into the text inputs
741 * // if values entered into the text inputs are 'foo' and 'bar'
744 * var v = $(':checkbox').fieldValue();
745 * // if neither checkbox is checked
747 * // if both checkboxes are checked
750 * var v = $(':radio').fieldValue();
751 * // if neither radio is checked
753 * // if first radio is checked
756 * The successful argument controls whether or not the field element must be 'successful'
757 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
758 * The default value of the successful argument is true. If this value is false the value(s)
759 * for each element is returned.
761 * Note: This method *always* returns an array. If no valid value can be determined the
762 * array will be empty, otherwise it will contain one or more values.
764 $.fn.fieldValue = function(successful) {
765 for (var val=[], i=0, max=this.length; i < max; i++) {
767 var v = $.fieldValue(el, successful);
768 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
771 v.constructor == Array ? $.merge(val, v) : val.push(v);
777 * Returns the value of the field element.
779 $.fieldValue = function(el, successful) {
780 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
781 if (successful === undefined) {
785 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
786 (t == 'checkbox' || t == 'radio') && !el.checked ||
787 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
788 tag == 'select' && el.selectedIndex == -1)) {
792 if (tag == 'select') {
793 var index = el.selectedIndex;
797 var a = [], ops = el.options;
798 var one = (t == 'select-one');
799 var max = (one ? index+1 : ops.length);
800 for(var i=(one ? index : 0); i < max; i++) {
804 if (!v) { // extra pain for IE...
805 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
819 * Clears the form data. Takes the following actions on the form's input fields:
820 * - input text fields will have their 'value' property set to the empty string
821 * - select elements will have their 'selectedIndex' property set to -1
822 * - checkbox and radio inputs will have their 'checked' property set to false
823 * - inputs of type submit, button, reset, and hidden will *not* be effected
824 * - button elements will *not* be effected
826 $.fn.clearForm = function() {
827 return this.each(function() {
828 $('input,select,textarea', this).clearFields();
833 * Clears the selected form elements.
835 $.fn.clearFields = $.fn.clearInputs = function() {
836 var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
837 return this.each(function() {
838 var t = this.type, tag = this.tagName.toLowerCase();
839 if (re.test(t) || tag == 'textarea') {
842 else if (t == 'checkbox' || t == 'radio') {
843 this.checked = false;
845 else if (tag == 'select') {
846 this.selectedIndex = -1;
852 * Resets the form data. Causes all form elements to be reset to their original value.
854 $.fn.resetForm = function() {
855 return this.each(function() {
856 // guard against an input with the name of 'reset'
857 // note that IE reports the reset function as an 'object'
858 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
865 * Enables or disables any matching elements.
867 $.fn.enable = function(b) {
868 if (b === undefined) {
871 return this.each(function() {
877 * Checks/unchecks any matching checkboxes or radio buttons and
878 * selects/deselects and matching option elements.
880 $.fn.selected = function(select) {
881 if (select === undefined) {
884 return this.each(function() {
886 if (t == 'checkbox' || t == 'radio') {
887 this.checked = select;
889 else if (this.tagName.toLowerCase() == 'option') {
890 var $sel = $(this).parent('select');
891 if (select && $sel[0] && $sel[0].type == 'select-one') {
892 // deselect all other options
893 $sel.find('option').selected(false);
895 this.selected = select;
900 // helper fn for console logging
902 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
903 if (window.console && window.console.log) {
904 window.console.log(msg);
906 else if (window.opera && window.opera.postError) {
907 window.opera.postError(msg);