3 * version: 2.52 (07-DEC-2010)
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 type: this.attr('method') || 'GET',
68 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
71 // hook for manipulating the form data before it is extracted;
72 // convenient for use with rich editors like tinyMCE or FCKEditor
74 this.trigger('form-pre-serialize', [this, options, veto]);
76 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
80 // provide opportunity to alter form data before it is serialized
81 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
82 log('ajaxSubmit: submit aborted via beforeSerialize callback');
86 var n,v,a = this.formToArray(options.semantic);
88 options.extraData = options.data;
89 for (n in options.data) {
90 if(options.data[n] instanceof Array) {
91 for (var k in options.data[n]) {
92 a.push( { name: n, value: options.data[n][k] } );
97 v = $.isFunction(v) ? v() : v; // if value is fn, invoke it
98 a.push( { name: n, value: v } );
103 // give pre-submit callback an opportunity to abort the submit
104 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
105 log('ajaxSubmit: submit aborted via beforeSubmit callback');
109 // fire vetoable 'validate' event
110 this.trigger('form-submit-validate', [a, this, options, veto]);
112 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
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'
126 var $form = this, callbacks = [];
127 if (options.resetForm) {
128 callbacks.push(function() { $form.resetForm(); });
130 if (options.clearForm) {
131 callbacks.push(function() { $form.clearForm(); });
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, fileUpload);
174 // fire 'notify' event
175 this.trigger('form-submit-notify', [this, options]);
179 // private function for handling file uploads (hat tip to YAHOO!)
180 function fileUpload() {
183 if ($(':input[name=submit],:input[id=submit]', form).length) {
184 // if there is an input with a name or id of 'submit' then we won't be
185 // able to invoke the submit fn on the form (at least not x-browser)
186 alert('Error: Form elements must not have name or id of "submit".');
190 var s = $.extend(true, {}, $.ajaxSettings, options);
191 s.context = s.context || s;
192 var id = 'jqFormIO' + (new Date().getTime()), fn = '_'+id;
193 window[fn] = function() {
194 var f = $io.data('form-plugin-onload');
197 window[fn] = undefined;
198 try { delete window[fn]; } catch(e){}
201 var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ s.iframeSrc +'" onload="window[\'_\'+this.id]()" />');
204 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
206 var xhr = { // mock object
212 getAllResponseHeaders: function() {},
213 getResponseHeader: function() {},
214 setRequestHeader: function() {},
217 $io.attr('src', s.iframeSrc); // abort op in progress
222 // trigger ajax global events so that activity/block indicators work like normal
223 if (g && ! $.active++) {
224 $.event.trigger("ajaxStart");
227 $.event.trigger("ajaxSend", [xhr, s]);
230 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
240 var cbInvoked = false;
243 // add submitting element to data if we know it
247 if (n && !sub.disabled) {
248 s.extraData = s.extraData || {};
249 s.extraData[n] = sub.value;
250 if (sub.type == "image") {
251 s.extraData[n+'.x'] = form.clk_x;
252 s.extraData[n+'.y'] = form.clk_y;
257 // take a breath so that pending repaints get some cpu time before the upload starts
258 function doSubmit() {
259 // make sure form attrs are set
260 var t = $form.attr('target'), a = $form.attr('action');
262 // update form attrs in IE friendly way
263 form.setAttribute('target',id);
264 if (form.getAttribute('method') != 'POST') {
265 form.setAttribute('method', 'POST');
267 if (form.getAttribute('action') != s.url) {
268 form.setAttribute('action', s.url);
271 // ie borks in some cases when setting encoding
272 if (! s.skipEncodingOverride) {
274 encoding: 'multipart/form-data',
275 enctype: 'multipart/form-data'
281 setTimeout(function() { timedOut = true; cb(); }, s.timeout);
284 // add "extra" data to form if provided in options
285 var extraInputs = [];
288 for (var n in s.extraData) {
290 $('<input type="hidden" name="'+n+'" value="'+s.extraData[n]+'" />')
295 // add iframe to doc and submit the form
296 $io.appendTo('body');
297 $io.data('form-plugin-onload', cb);
301 // reset attrs and remove "extra" input elements
302 form.setAttribute('action',a);
304 form.setAttribute('target', t);
306 $form.removeAttr('target');
308 $(extraInputs).remove();
316 setTimeout(doSubmit, 10); // this lets dom updates render
319 var data, doc, domCheckCount = 50;
326 $io.removeData('form-plugin-onload');
333 // extract the server response from the iframe
334 doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
336 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
338 if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
339 if (--domCheckCount) {
340 // in some browsers (Opera) the iframe DOM is not always traversable when
341 // the onload callback fires, so we loop a bit to accommodate
342 log('requeing onLoad callback, DOM not available');
346 // let this fall through because server response could be an empty document
347 //log('Could not access iframe DOM after mutiple tries.');
348 //throw 'DOMException: not available';
351 //log('response detected');
353 xhr.responseText = doc.documentElement ? doc.documentElement.innerHTML : null;
354 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
355 xhr.getResponseHeader = function(header){
356 var headers = {'content-type': s.dataType};
357 return headers[header];
360 var scr = /(json|script)/.test(s.dataType);
361 if (scr || s.textarea) {
362 // see if user embedded response in textarea
363 var ta = doc.getElementsByTagName('textarea')[0];
365 xhr.responseText = ta.value;
368 // account for browsers injecting pre around json response
369 var pre = doc.getElementsByTagName('pre')[0];
370 var b = doc.getElementsByTagName('body')[0];
372 xhr.responseText = pre.textContent;
375 xhr.responseText = b.innerHTML;
379 else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
380 xhr.responseXML = toXml(xhr.responseText);
382 data = $.httpData(xhr, s.dataType);
385 log('error caught:',e);
388 $.handleError(s, xhr, 'error', e);
392 log('upload aborted');
396 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
398 s.success.call(s.context, data, 'success', xhr);
400 $.event.trigger("ajaxSuccess", [xhr, s]);
404 $.event.trigger("ajaxComplete", [xhr, s]);
406 if (g && ! --$.active) {
407 $.event.trigger("ajaxStop");
410 s.complete.call(s.context, xhr, ok ? 'success' : 'error');
414 setTimeout(function() {
415 $io.removeData('form-plugin-onload');
417 xhr.responseXML = null;
421 function toXml(s, doc) {
422 if (window.ActiveXObject) {
423 doc = new ActiveXObject('Microsoft.XMLDOM');
428 doc = (new DOMParser()).parseFromString(s, 'text/xml');
430 return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
436 * ajaxForm() provides a mechanism for fully automating form submission.
438 * The advantages of using this method instead of ajaxSubmit() are:
440 * 1: This method will include coordinates for <input type="image" /> elements (if the element
441 * is used to submit the form).
442 * 2. This method will include the submit element's name/value data (for the element that was
443 * used to submit the form).
444 * 3. This method binds the submit() method to the form for you.
446 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
447 * passes the options argument along after properly binding events for submit elements and
450 $.fn.ajaxForm = function(options) {
451 // in jQuery 1.3+ we can fix mistakes with the ready state
452 if (this.length === 0) {
453 var o = { s: this.selector, c: this.context };
454 if (!$.isReady && o.s) {
455 log('DOM not ready, queuing ajaxForm');
457 $(o.s,o.c).ajaxForm(options);
461 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
462 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
466 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
467 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
469 $(this).ajaxSubmit(options);
471 }).bind('click.form-plugin', function(e) {
472 var target = e.target;
474 if (!($el.is(":submit,input:image"))) {
475 // is this a child element of the submit el? (ex: a span within a button)
476 var t = $el.closest(':submit');
484 if (target.type == 'image') {
485 if (e.offsetX != undefined) {
486 form.clk_x = e.offsetX;
487 form.clk_y = e.offsetY;
488 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
489 var offset = $el.offset();
490 form.clk_x = e.pageX - offset.left;
491 form.clk_y = e.pageY - offset.top;
493 form.clk_x = e.pageX - target.offsetLeft;
494 form.clk_y = e.pageY - target.offsetTop;
498 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
502 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
503 $.fn.ajaxFormUnbind = function() {
504 return this.unbind('submit.form-plugin click.form-plugin');
508 * formToArray() gathers form element data into an array of objects that can
509 * be passed to any of the following ajax functions: $.get, $.post, or load.
510 * Each object in the array has both a 'name' and 'value' property. An example of
511 * an array for a simple login form might be:
513 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
515 * It is this array that is passed to pre-submit callback functions provided to the
516 * ajaxSubmit() and ajaxForm() methods.
518 $.fn.formToArray = function(semantic) {
520 if (this.length === 0) {
525 var els = semantic ? form.getElementsByTagName('*') : form.elements;
530 var i,j,n,v,el,max,jmax;
531 for(i=0, max=els.length; i < max; i++) {
538 if (semantic && form.clk && el.type == "image") {
539 // handle image inputs on the fly when semantic == true
540 if(!el.disabled && form.clk == el) {
541 a.push({name: n, value: $(el).val()});
542 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
547 v = $.fieldValue(el, true);
548 if (v && v.constructor == Array) {
549 for(j=0, jmax=v.length; j < jmax; j++) {
550 a.push({name: n, value: v[j]});
553 else if (v !== null && typeof v != 'undefined') {
554 a.push({name: n, value: v});
558 if (!semantic && form.clk) {
559 // input type=='image' are not found in elements array! handle it here
560 var $input = $(form.clk), input = $input[0];
562 if (n && !input.disabled && input.type == 'image') {
563 a.push({name: n, value: $input.val()});
564 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
571 * Serializes form data into a 'submittable' string. This method will return a string
572 * in the format: name1=value1&name2=value2
574 $.fn.formSerialize = function(semantic) {
575 //hand off to jQuery.param for proper encoding
576 return $.param(this.formToArray(semantic));
580 * Serializes all field elements in the jQuery object into a query string.
581 * This method will return a string in the format: name1=value1&name2=value2
583 $.fn.fieldSerialize = function(successful) {
585 this.each(function() {
590 var v = $.fieldValue(this, successful);
591 if (v && v.constructor == Array) {
592 for (var i=0,max=v.length; i < max; i++) {
593 a.push({name: n, value: v[i]});
596 else if (v !== null && typeof v != 'undefined') {
597 a.push({name: this.name, value: v});
600 //hand off to jQuery.param for proper encoding
605 * Returns the value(s) of the element in the matched set. For example, consider the following form:
608 * <input name="A" type="text" />
609 * <input name="A" type="text" />
610 * <input name="B" type="checkbox" value="B1" />
611 * <input name="B" type="checkbox" value="B2"/>
612 * <input name="C" type="radio" value="C1" />
613 * <input name="C" type="radio" value="C2" />
616 * var v = $(':text').fieldValue();
617 * // if no values are entered into the text inputs
619 * // if values entered into the text inputs are 'foo' and 'bar'
622 * var v = $(':checkbox').fieldValue();
623 * // if neither checkbox is checked
625 * // if both checkboxes are checked
628 * var v = $(':radio').fieldValue();
629 * // if neither radio is checked
631 * // if first radio is checked
634 * The successful argument controls whether or not the field element must be 'successful'
635 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
636 * The default value of the successful argument is true. If this value is false the value(s)
637 * for each element is returned.
639 * Note: This method *always* returns an array. If no valid value can be determined the
640 * array will be empty, otherwise it will contain one or more values.
642 $.fn.fieldValue = function(successful) {
643 for (var val=[], i=0, max=this.length; i < max; i++) {
645 var v = $.fieldValue(el, successful);
646 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
649 v.constructor == Array ? $.merge(val, v) : val.push(v);
655 * Returns the value of the field element.
657 $.fieldValue = function(el, successful) {
658 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
659 if (successful === undefined) {
663 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
664 (t == 'checkbox' || t == 'radio') && !el.checked ||
665 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
666 tag == 'select' && el.selectedIndex == -1)) {
670 if (tag == 'select') {
671 var index = el.selectedIndex;
675 var a = [], ops = el.options;
676 var one = (t == 'select-one');
677 var max = (one ? index+1 : ops.length);
678 for(var i=(one ? index : 0); i < max; i++) {
682 if (!v) { // extra pain for IE...
683 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
697 * Clears the form data. Takes the following actions on the form's input fields:
698 * - input text fields will have their 'value' property set to the empty string
699 * - select elements will have their 'selectedIndex' property set to -1
700 * - checkbox and radio inputs will have their 'checked' property set to false
701 * - inputs of type submit, button, reset, and hidden will *not* be effected
702 * - button elements will *not* be effected
704 $.fn.clearForm = function() {
705 return this.each(function() {
706 $('input,select,textarea', this).clearFields();
711 * Clears the selected form elements.
713 $.fn.clearFields = $.fn.clearInputs = function() {
714 return this.each(function() {
715 var t = this.type, tag = this.tagName.toLowerCase();
716 if (t == 'text' || t == 'password' || tag == 'textarea') {
719 else if (t == 'checkbox' || t == 'radio') {
720 this.checked = false;
722 else if (tag == 'select') {
723 this.selectedIndex = -1;
729 * Resets the form data. Causes all form elements to be reset to their original value.
731 $.fn.resetForm = function() {
732 return this.each(function() {
733 // guard against an input with the name of 'reset'
734 // note that IE reports the reset function as an 'object'
735 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
742 * Enables or disables any matching elements.
744 $.fn.enable = function(b) {
745 if (b === undefined) {
748 return this.each(function() {
754 * Checks/unchecks any matching checkboxes or radio buttons and
755 * selects/deselects and matching option elements.
757 $.fn.selected = function(select) {
758 if (select === undefined) {
761 return this.each(function() {
763 if (t == 'checkbox' || t == 'radio') {
764 this.checked = select;
766 else if (this.tagName.toLowerCase() == 'option') {
767 var $sel = $(this).parent('select');
768 if (select && $sel[0] && $sel[0].type == 'select-one') {
769 // deselect all other options
770 $sel.find('option').selected(false);
772 this.selected = select;
777 // helper fn for console logging
778 // set $.fn.ajaxSubmit.debug to true to enable debug logging
780 if ($.fn.ajaxSubmit.debug) {
781 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
782 if (window.console && window.console.log) {
783 window.console.log(msg);
785 else if (window.opera && window.opera.postError) {
786 window.opera.postError(msg);