3 * version: 2.63 (29-JAN-2011)
4 * @requires jQuery v1.3.2 or later
6 * Examples and documentation at: http://malsup.com/jquery/form/
7 * Dual licensed under the MIT and GPL licenses:
8 * http://www.opensource.org/licenses/mit-license.php
9 * http://www.gnu.org/licenses/gpl.html
16 Do not use both ajaxSubmit and ajaxForm on the same form. These
17 functions are intended to be exclusive. Use ajaxSubmit if you want
18 to bind your own submit handler to the form. For example,
20 $(document).ready(function() {
21 $('#myForm').bind('submit', function(e) {
22 e.preventDefault(); // <-- important
29 Use ajaxForm when you want the plugin to manage all the event binding
32 $(document).ready(function() {
33 $('#myForm').ajaxForm({
38 When using ajaxForm, the ajaxSubmit function will be invoked for you
39 at the appropriate time.
43 * ajaxSubmit() provides a mechanism for immediately submitting
44 * an HTML form using AJAX.
46 $.fn.ajaxSubmit = function(options) {
47 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
49 log('ajaxSubmit: skipping submit process - no element selected');
53 if (typeof options == 'function') {
54 options = { success: options };
57 var action = this.attr('action');
58 var url = (typeof action === 'string') ? $.trim(action) : '';
60 // clean url (don't include hash vaue)
61 url = (url.match(/^([^#]+)/)||[])[1];
63 url = url || window.location.href || '';
65 options = $.extend(true, {
67 type: this[0].getAttribute('method') || 'GET', // IE7 massage (see issue 57)
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 var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ s.iframeSrc +'" />');
196 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
198 var xhr = { // mock object
204 getAllResponseHeaders: function() {},
205 getResponseHeader: function() {},
206 setRequestHeader: function() {},
209 $io.attr('src', s.iframeSrc); // abort op in progress
214 // trigger ajax global events so that activity/block indicators work like normal
215 if (g && ! $.active++) {
216 $.event.trigger("ajaxStart");
219 $.event.trigger("ajaxSend", [xhr, s]);
222 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
234 // add submitting element to data if we know it
238 if (n && !sub.disabled) {
239 s.extraData = s.extraData || {};
240 s.extraData[n] = sub.value;
241 if (sub.type == "image") {
242 s.extraData[n+'.x'] = form.clk_x;
243 s.extraData[n+'.y'] = form.clk_y;
248 // take a breath so that pending repaints get some cpu time before the upload starts
249 function doSubmit() {
250 // make sure form attrs are set
251 var t = $form.attr('target'), a = $form.attr('action');
253 // update form attrs in IE friendly way
254 form.setAttribute('target',id);
255 if (form.getAttribute('method') != 'POST') {
256 form.setAttribute('method', 'POST');
258 if (form.getAttribute('action') != s.url) {
259 form.setAttribute('action', s.url);
262 // ie borks in some cases when setting encoding
263 if (! s.skipEncodingOverride) {
265 encoding: 'multipart/form-data',
266 enctype: 'multipart/form-data'
272 setTimeout(function() { timedOut = true; cb(); }, s.timeout);
275 // add "extra" data to form if provided in options
276 var extraInputs = [];
279 for (var n in s.extraData) {
281 $('<input type="hidden" name="'+n+'" value="'+s.extraData[n]+'" />')
286 // add iframe to doc and submit the form
287 $io.appendTo('body');
288 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
292 // reset attrs and remove "extra" input elements
293 form.setAttribute('action',a);
295 form.setAttribute('target', t);
297 $form.removeAttr('target');
299 $(extraInputs).remove();
307 setTimeout(doSubmit, 10); // this lets dom updates render
310 var data, doc, domCheckCount = 50;
313 doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
314 if (!doc || doc.location.href == s.iframeSrc) {
315 // response not received yet
318 io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
326 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
328 if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
329 if (--domCheckCount) {
330 // in some browsers (Opera) the iframe DOM is not always traversable when
331 // the onload callback fires, so we loop a bit to accommodate
332 log('requeing onLoad callback, DOM not available');
336 // let this fall through because server response could be an empty document
337 //log('Could not access iframe DOM after mutiple tries.');
338 //throw 'DOMException: not available';
341 //log('response detected');
342 xhr.responseText = doc.body ? doc.body.innerHTML : doc.documentElement ? doc.documentElement.innerHTML : null;
343 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
344 xhr.getResponseHeader = function(header){
345 var headers = {'content-type': s.dataType};
346 return headers[header];
349 var scr = /(json|script)/.test(s.dataType);
350 if (scr || s.textarea) {
351 // see if user embedded response in textarea
352 var ta = doc.getElementsByTagName('textarea')[0];
354 xhr.responseText = ta.value;
357 // account for browsers injecting pre around json response
358 var pre = doc.getElementsByTagName('pre')[0];
359 var b = doc.getElementsByTagName('body')[0];
361 xhr.responseText = pre.textContent;
364 xhr.responseText = b.innerHTML;
368 else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
369 xhr.responseXML = toXml(xhr.responseText);
372 data = httpData(xhr, s.dataType, s);
375 log('error caught:',e);
378 s.error.call(s.context, xhr, 'error', e);
379 g && $.event.trigger("ajaxError", [xhr, s, e]);
383 log('upload aborted');
387 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
389 s.success.call(s.context, data, 'success', xhr);
390 g && $.event.trigger("ajaxSuccess", [xhr, s]);
393 g && $.event.trigger("ajaxComplete", [xhr, s]);
395 if (g && ! --$.active) {
396 $.event.trigger("ajaxStop");
399 s.complete && s.complete.call(s.context, xhr, ok ? 'success' : 'error');
402 setTimeout(function() {
403 $io.removeData('form-plugin-onload');
405 xhr.responseXML = null;
409 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
410 if (window.ActiveXObject) {
411 doc = new ActiveXObject('Microsoft.XMLDOM');
416 doc = (new DOMParser()).parseFromString(s, 'text/xml');
418 return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
420 var parseJSON = $.parseJSON || function(s) {
421 return window['eval']('(' + s + ')');
424 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
425 var ct = xhr.getResponseHeader('content-type') || '',
426 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
427 data = xml ? xhr.responseXML : xhr.responseText;
429 if (xml && data.documentElement.nodeName === 'parsererror') {
430 $.error && $.error('parsererror');
432 if (s && s.dataFilter) {
433 data = s.dataFilter(data, type);
435 if (typeof data === 'string') {
436 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
437 data = parseJSON(data);
438 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
448 * ajaxForm() provides a mechanism for fully automating form submission.
450 * The advantages of using this method instead of ajaxSubmit() are:
452 * 1: This method will include coordinates for <input type="image" /> elements (if the element
453 * is used to submit the form).
454 * 2. This method will include the submit element's name/value data (for the element that was
455 * used to submit the form).
456 * 3. This method binds the submit() method to the form for you.
458 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
459 * passes the options argument along after properly binding events for submit elements and
462 $.fn.ajaxForm = function(options) {
463 // in jQuery 1.3+ we can fix mistakes with the ready state
464 if (this.length === 0) {
465 var o = { s: this.selector, c: this.context };
466 if (!$.isReady && o.s) {
467 log('DOM not ready, queuing ajaxForm');
469 $(o.s,o.c).ajaxForm(options);
473 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
474 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
478 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
479 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
481 $(this).ajaxSubmit(options);
483 }).bind('click.form-plugin', function(e) {
484 var target = e.target;
486 if (!($el.is(":submit,input:image"))) {
487 // is this a child element of the submit el? (ex: a span within a button)
488 var t = $el.closest(':submit');
496 if (target.type == 'image') {
497 if (e.offsetX != undefined) {
498 form.clk_x = e.offsetX;
499 form.clk_y = e.offsetY;
500 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
501 var offset = $el.offset();
502 form.clk_x = e.pageX - offset.left;
503 form.clk_y = e.pageY - offset.top;
505 form.clk_x = e.pageX - target.offsetLeft;
506 form.clk_y = e.pageY - target.offsetTop;
510 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
514 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
515 $.fn.ajaxFormUnbind = function() {
516 return this.unbind('submit.form-plugin click.form-plugin');
520 * formToArray() gathers form element data into an array of objects that can
521 * be passed to any of the following ajax functions: $.get, $.post, or load.
522 * Each object in the array has both a 'name' and 'value' property. An example of
523 * an array for a simple login form might be:
525 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
527 * It is this array that is passed to pre-submit callback functions provided to the
528 * ajaxSubmit() and ajaxForm() methods.
530 $.fn.formToArray = function(semantic) {
532 if (this.length === 0) {
537 var els = semantic ? form.getElementsByTagName('*') : form.elements;
542 var i,j,n,v,el,max,jmax;
543 for(i=0, max=els.length; i < max; i++) {
550 if (semantic && form.clk && el.type == "image") {
551 // handle image inputs on the fly when semantic == true
552 if(!el.disabled && form.clk == el) {
553 a.push({name: n, value: $(el).val()});
554 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
559 v = $.fieldValue(el, true);
560 if (v && v.constructor == Array) {
561 for(j=0, jmax=v.length; j < jmax; j++) {
562 a.push({name: n, value: v[j]});
565 else if (v !== null && typeof v != 'undefined') {
566 a.push({name: n, value: v});
570 if (!semantic && form.clk) {
571 // input type=='image' are not found in elements array! handle it here
572 var $input = $(form.clk), input = $input[0];
574 if (n && !input.disabled && input.type == 'image') {
575 a.push({name: n, value: $input.val()});
576 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
583 * Serializes form data into a 'submittable' string. This method will return a string
584 * in the format: name1=value1&name2=value2
586 $.fn.formSerialize = function(semantic) {
587 //hand off to jQuery.param for proper encoding
588 return $.param(this.formToArray(semantic));
592 * Serializes all field elements in the jQuery object into a query string.
593 * This method will return a string in the format: name1=value1&name2=value2
595 $.fn.fieldSerialize = function(successful) {
597 this.each(function() {
602 var v = $.fieldValue(this, successful);
603 if (v && v.constructor == Array) {
604 for (var i=0,max=v.length; i < max; i++) {
605 a.push({name: n, value: v[i]});
608 else if (v !== null && typeof v != 'undefined') {
609 a.push({name: this.name, value: v});
612 //hand off to jQuery.param for proper encoding
617 * Returns the value(s) of the element in the matched set. For example, consider the following form:
620 * <input name="A" type="text" />
621 * <input name="A" type="text" />
622 * <input name="B" type="checkbox" value="B1" />
623 * <input name="B" type="checkbox" value="B2"/>
624 * <input name="C" type="radio" value="C1" />
625 * <input name="C" type="radio" value="C2" />
628 * var v = $(':text').fieldValue();
629 * // if no values are entered into the text inputs
631 * // if values entered into the text inputs are 'foo' and 'bar'
634 * var v = $(':checkbox').fieldValue();
635 * // if neither checkbox is checked
637 * // if both checkboxes are checked
640 * var v = $(':radio').fieldValue();
641 * // if neither radio is checked
643 * // if first radio is checked
646 * The successful argument controls whether or not the field element must be 'successful'
647 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
648 * The default value of the successful argument is true. If this value is false the value(s)
649 * for each element is returned.
651 * Note: This method *always* returns an array. If no valid value can be determined the
652 * array will be empty, otherwise it will contain one or more values.
654 $.fn.fieldValue = function(successful) {
655 for (var val=[], i=0, max=this.length; i < max; i++) {
657 var v = $.fieldValue(el, successful);
658 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
661 v.constructor == Array ? $.merge(val, v) : val.push(v);
667 * Returns the value of the field element.
669 $.fieldValue = function(el, successful) {
670 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
671 if (successful === undefined) {
675 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
676 (t == 'checkbox' || t == 'radio') && !el.checked ||
677 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
678 tag == 'select' && el.selectedIndex == -1)) {
682 if (tag == 'select') {
683 var index = el.selectedIndex;
687 var a = [], ops = el.options;
688 var one = (t == 'select-one');
689 var max = (one ? index+1 : ops.length);
690 for(var i=(one ? index : 0); i < max; i++) {
694 if (!v) { // extra pain for IE...
695 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
709 * Clears the form data. Takes the following actions on the form's input fields:
710 * - input text fields will have their 'value' property set to the empty string
711 * - select elements will have their 'selectedIndex' property set to -1
712 * - checkbox and radio inputs will have their 'checked' property set to false
713 * - inputs of type submit, button, reset, and hidden will *not* be effected
714 * - button elements will *not* be effected
716 $.fn.clearForm = function() {
717 return this.each(function() {
718 $('input,select,textarea', this).clearFields();
723 * Clears the selected form elements.
725 $.fn.clearFields = $.fn.clearInputs = function() {
726 return this.each(function() {
727 var t = this.type, tag = this.tagName.toLowerCase();
728 if (t == 'text' || t == 'password' || tag == 'textarea') {
731 else if (t == 'checkbox' || t == 'radio') {
732 this.checked = false;
734 else if (tag == 'select') {
735 this.selectedIndex = -1;
741 * Resets the form data. Causes all form elements to be reset to their original value.
743 $.fn.resetForm = function() {
744 return this.each(function() {
745 // guard against an input with the name of 'reset'
746 // note that IE reports the reset function as an 'object'
747 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
754 * Enables or disables any matching elements.
756 $.fn.enable = function(b) {
757 if (b === undefined) {
760 return this.each(function() {
766 * Checks/unchecks any matching checkboxes or radio buttons and
767 * selects/deselects and matching option elements.
769 $.fn.selected = function(select) {
770 if (select === undefined) {
773 return this.each(function() {
775 if (t == 'checkbox' || t == 'radio') {
776 this.checked = select;
778 else if (this.tagName.toLowerCase() == 'option') {
779 var $sel = $(this).parent('select');
780 if (select && $sel[0] && $sel[0].type == 'select-one') {
781 // deselect all other options
782 $sel.find('option').selected(false);
784 this.selected = select;
789 // helper fn for console logging
790 // set $.fn.ajaxSubmit.debug to true to enable debug logging
792 if ($.fn.ajaxSubmit.debug) {
793 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
794 if (window.console && window.console.log) {
795 window.console.log(msg);
797 else if (window.opera && window.opera.postError) {
798 window.opera.postError(msg);