3 * version: 2.21 (08-FEB-2009)
\r
4 * @requires jQuery v1.2.2 or later
\r
6 * Examples and documentation at: http://malsup.com/jquery/form/
\r
7 * Dual licensed under the MIT and GPL licenses:
\r
8 * http://www.opensource.org/licenses/mit-license.php
\r
9 * http://www.gnu.org/licenses/gpl.html
\r
16 Do not use both ajaxSubmit and ajaxForm on the same form. These
\r
17 functions are intended to be exclusive. Use ajaxSubmit if you want
\r
18 to bind your own submit handler to the form. For example,
\r
20 $(document).ready(function() {
\r
21 $('#myForm').bind('submit', function() {
\r
22 $(this).ajaxSubmit({
\r
25 return false; // <-- important!
\r
29 Use ajaxForm when you want the plugin to manage all the event binding
\r
30 for you. For example,
\r
32 $(document).ready(function() {
\r
33 $('#myForm').ajaxForm({
\r
38 When using ajaxForm, the ajaxSubmit function will be invoked for you
\r
39 at the appropriate time.
\r
43 * ajaxSubmit() provides a mechanism for immediately submitting
\r
44 * an HTML form using AJAX.
\r
46 $.fn.ajaxSubmit = function(options) {
\r
47 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
\r
49 log('ajaxSubmit: skipping submit process - no element selected');
\r
53 if (typeof options == 'function')
\r
54 options = { success: options };
\r
56 options = $.extend({
\r
57 url: this.attr('action') || window.location.toString(),
\r
58 type: this.attr('method') || 'GET'
\r
61 // hook for manipulating the form data before it is extracted;
\r
62 // convenient for use with rich editors like tinyMCE or FCKEditor
\r
64 this.trigger('form-pre-serialize', [this, options, veto]);
\r
66 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
\r
70 // provide opportunity to alter form data before it is serialized
\r
71 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
\r
72 log('ajaxSubmit: submit aborted via beforeSerialize callback');
\r
76 var a = this.formToArray(options.semantic);
\r
78 options.extraData = options.data;
\r
79 for (var n in options.data) {
\r
80 if(options.data[n] instanceof Array) {
\r
81 for (var k in options.data[n])
\r
82 a.push( { name: n, value: options.data[n][k] } )
\r
85 a.push( { name: n, value: options.data[n] } );
\r
89 // give pre-submit callback an opportunity to abort the submit
\r
90 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
\r
91 log('ajaxSubmit: submit aborted via beforeSubmit callback');
\r
95 // fire vetoable 'validate' event
\r
96 this.trigger('form-submit-validate', [a, this, options, veto]);
\r
98 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
\r
102 var q = $.param(a);
\r
104 if (options.type.toUpperCase() == 'GET') {
\r
105 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
\r
106 options.data = null; // data is null for 'get'
\r
109 options.data = q; // data is the query string for 'post'
\r
111 var $form = this, callbacks = [];
\r
112 if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
\r
113 if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
\r
115 // perform a load on the target only if dataType is not provided
\r
116 if (!options.dataType && options.target) {
\r
117 var oldSuccess = options.success || function(){};
\r
118 callbacks.push(function(data) {
\r
119 $(options.target).html(data).each(oldSuccess, arguments);
\r
122 else if (options.success)
\r
123 callbacks.push(options.success);
\r
125 options.success = function(data, status) {
\r
126 for (var i=0, max=callbacks.length; i < max; i++)
\r
127 callbacks[i].apply(options, [data, status, $form]);
\r
130 // are there files to upload?
\r
131 var files = $('input:file', this).fieldValue();
\r
133 for (var j=0; j < files.length; j++)
\r
137 // options.iframe allows user to force iframe mode
\r
138 if (options.iframe || found) {
\r
139 // hack to fix Safari hang (thanks to Tim Molendijk for this)
\r
140 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
\r
141 if (options.closeKeepAlive)
\r
142 $.get(options.closeKeepAlive, fileUpload);
\r
149 // fire 'notify' event
\r
150 this.trigger('form-submit-notify', [this, options]);
\r
154 // private function for handling file uploads (hat tip to YAHOO!)
\r
155 function fileUpload() {
\r
156 var form = $form[0];
\r
158 if ($(':input[name=submit]', form).length) {
\r
159 alert('Error: Form elements must not be named "submit".');
\r
163 var opts = $.extend({}, $.ajaxSettings, options);
\r
164 var s = jQuery.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
\r
166 var id = 'jqFormIO' + (new Date().getTime());
\r
167 var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
\r
170 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
\r
172 var xhr = { // mock object
\r
174 responseText: null,
\r
178 getAllResponseHeaders: function() {},
\r
179 getResponseHeader: function() {},
\r
180 setRequestHeader: function() {},
\r
181 abort: function() {
\r
183 $io.attr('src','about:blank'); // abort op in progress
\r
187 var g = opts.global;
\r
188 // trigger ajax global events so that activity/block indicators work like normal
\r
189 if (g && ! $.active++) $.event.trigger("ajaxStart");
\r
190 if (g) $.event.trigger("ajaxSend", [xhr, opts]);
\r
192 if (s.beforeSend && s.beforeSend(xhr, s) === false) {
\r
193 s.global && jQuery.active--;
\r
202 // add submitting element to data if we know it
\r
203 var sub = form.clk;
\r
206 if (n && !sub.disabled) {
\r
207 options.extraData = options.extraData || {};
\r
208 options.extraData[n] = sub.value;
\r
209 if (sub.type == "image") {
\r
210 options.extraData[name+'.x'] = form.clk_x;
\r
211 options.extraData[name+'.y'] = form.clk_y;
\r
216 // take a breath so that pending repaints get some cpu time before the upload starts
\r
217 setTimeout(function() {
\r
218 // make sure form attrs are set
\r
219 var t = $form.attr('target'), a = $form.attr('action');
\r
221 // update form attrs in IE friendly way
\r
222 form.setAttribute('target',id);
\r
223 if (form.getAttribute('method') != 'POST')
\r
224 form.setAttribute('method', 'POST');
\r
225 if (form.getAttribute('action') != opts.url)
\r
226 form.setAttribute('action', opts.url);
\r
228 // ie borks in some cases when setting encoding
\r
229 if (! options.skipEncodingOverride) {
\r
231 encoding: 'multipart/form-data',
\r
232 enctype: 'multipart/form-data'
\r
238 setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
\r
240 // add "extra" data to form if provided in options
\r
241 var extraInputs = [];
\r
243 if (options.extraData)
\r
244 for (var n in options.extraData)
\r
246 $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
\r
247 .appendTo(form)[0]);
\r
249 // add iframe to doc and submit the form
\r
250 $io.appendTo('body');
\r
251 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
\r
255 // reset attrs and remove "extra" input elements
\r
256 form.setAttribute('action',a);
\r
257 t ? form.setAttribute('target', t) : $form.removeAttr('target');
\r
258 $(extraInputs).remove();
\r
262 var nullCheckFlag = 0;
\r
265 if (cbInvoked++) return;
\r
267 io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
\r
271 if (timedOut) throw 'timeout';
\r
272 // extract the server response from the iframe
\r
275 doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
\r
277 if ((doc.body == null || doc.body.innerHTML == '') && !nullCheckFlag) {
\r
278 // in some browsers (cough, Opera 9.2.x) the iframe DOM is not always traversable when
\r
279 // the onload callback fires, so we give them a 2nd chance
\r
282 setTimeout(cb, 100);
\r
286 xhr.responseText = doc.body ? doc.body.innerHTML : null;
\r
287 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
\r
288 xhr.getResponseHeader = function(header){
\r
289 var headers = {'content-type': opts.dataType};
\r
290 return headers[header];
\r
293 if (opts.dataType == 'json' || opts.dataType == 'script') {
\r
294 var ta = doc.getElementsByTagName('textarea')[0];
\r
295 xhr.responseText = ta ? ta.value : xhr.responseText;
\r
297 else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
\r
298 xhr.responseXML = toXml(xhr.responseText);
\r
300 data = $.httpData(xhr, opts.dataType);
\r
304 $.handleError(opts, xhr, 'error', e);
\r
307 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
\r
309 opts.success(data, 'success');
\r
310 if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
\r
312 if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
\r
313 if (g && ! --$.active) $.event.trigger("ajaxStop");
\r
314 if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
\r
317 setTimeout(function() {
\r
319 xhr.responseXML = null;
\r
323 function toXml(s, doc) {
\r
324 if (window.ActiveXObject) {
\r
325 doc = new ActiveXObject('Microsoft.XMLDOM');
\r
326 doc.async = 'false';
\r
330 doc = (new DOMParser()).parseFromString(s, 'text/xml');
\r
331 return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
\r
337 * ajaxForm() provides a mechanism for fully automating form submission.
\r
339 * The advantages of using this method instead of ajaxSubmit() are:
\r
341 * 1: This method will include coordinates for <input type="image" /> elements (if the element
\r
342 * is used to submit the form).
\r
343 * 2. This method will include the submit element's name/value data (for the element that was
\r
344 * used to submit the form).
\r
345 * 3. This method binds the submit() method to the form for you.
\r
347 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
\r
348 * passes the options argument along after properly binding events for submit elements and
\r
351 $.fn.ajaxForm = function(options) {
\r
352 return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
\r
353 $(this).ajaxSubmit(options);
\r
355 }).each(function() {
\r
356 // store options in hash
\r
357 $(":submit,input:image", this).bind('click.form-plugin',function(e) {
\r
358 var form = this.form;
\r
360 if (this.type == 'image') {
\r
361 if (e.offsetX != undefined) {
\r
362 form.clk_x = e.offsetX;
\r
363 form.clk_y = e.offsetY;
\r
364 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
\r
365 var offset = $(this).offset();
\r
366 form.clk_x = e.pageX - offset.left;
\r
367 form.clk_y = e.pageY - offset.top;
\r
369 form.clk_x = e.pageX - this.offsetLeft;
\r
370 form.clk_y = e.pageY - this.offsetTop;
\r
374 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
\r
379 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
\r
380 $.fn.ajaxFormUnbind = function() {
\r
381 this.unbind('submit.form-plugin');
\r
382 return this.each(function() {
\r
383 $(":submit,input:image", this).unbind('click.form-plugin');
\r
389 * formToArray() gathers form element data into an array of objects that can
\r
390 * be passed to any of the following ajax functions: $.get, $.post, or load.
\r
391 * Each object in the array has both a 'name' and 'value' property. An example of
\r
392 * an array for a simple login form might be:
\r
394 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
\r
396 * It is this array that is passed to pre-submit callback functions provided to the
\r
397 * ajaxSubmit() and ajaxForm() methods.
\r
399 $.fn.formToArray = function(semantic) {
\r
401 if (this.length == 0) return a;
\r
403 var form = this[0];
\r
404 var els = semantic ? form.getElementsByTagName('*') : form.elements;
\r
405 if (!els) return a;
\r
406 for(var i=0, max=els.length; i < max; i++) {
\r
411 if (semantic && form.clk && el.type == "image") {
\r
412 // handle image inputs on the fly when semantic == true
\r
413 if(!el.disabled && form.clk == el)
\r
414 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
\r
418 var v = $.fieldValue(el, true);
\r
419 if (v && v.constructor == Array) {
\r
420 for(var j=0, jmax=v.length; j < jmax; j++)
\r
421 a.push({name: n, value: v[j]});
\r
423 else if (v !== null && typeof v != 'undefined')
\r
424 a.push({name: n, value: v});
\r
427 if (!semantic && form.clk) {
\r
428 // input type=='image' are not found in elements array! handle them here
\r
429 var inputs = form.getElementsByTagName("input");
\r
430 for(var i=0, max=inputs.length; i < max; i++) {
\r
431 var input = inputs[i];
\r
432 var n = input.name;
\r
433 if(n && !input.disabled && input.type == "image" && form.clk == input)
\r
434 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
\r
441 * Serializes form data into a 'submittable' string. This method will return a string
\r
442 * in the format: name1=value1&name2=value2
\r
444 $.fn.formSerialize = function(semantic) {
\r
445 //hand off to jQuery.param for proper encoding
\r
446 return $.param(this.formToArray(semantic));
\r
450 * Serializes all field elements in the jQuery object into a query string.
\r
451 * This method will return a string in the format: name1=value1&name2=value2
\r
453 $.fn.fieldSerialize = function(successful) {
\r
455 this.each(function() {
\r
458 var v = $.fieldValue(this, successful);
\r
459 if (v && v.constructor == Array) {
\r
460 for (var i=0,max=v.length; i < max; i++)
\r
461 a.push({name: n, value: v[i]});
\r
463 else if (v !== null && typeof v != 'undefined')
\r
464 a.push({name: this.name, value: v});
\r
466 //hand off to jQuery.param for proper encoding
\r
471 * Returns the value(s) of the element in the matched set. For example, consider the following form:
\r
474 * <input name="A" type="text" />
\r
475 * <input name="A" type="text" />
\r
476 * <input name="B" type="checkbox" value="B1" />
\r
477 * <input name="B" type="checkbox" value="B2"/>
\r
478 * <input name="C" type="radio" value="C1" />
\r
479 * <input name="C" type="radio" value="C2" />
\r
480 * </fieldset></form>
\r
482 * var v = $(':text').fieldValue();
\r
483 * // if no values are entered into the text inputs
\r
485 * // if values entered into the text inputs are 'foo' and 'bar'
\r
486 * v == ['foo','bar']
\r
488 * var v = $(':checkbox').fieldValue();
\r
489 * // if neither checkbox is checked
\r
491 * // if both checkboxes are checked
\r
492 * v == ['B1', 'B2']
\r
494 * var v = $(':radio').fieldValue();
\r
495 * // if neither radio is checked
\r
497 * // if first radio is checked
\r
500 * The successful argument controls whether or not the field element must be 'successful'
\r
501 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
\r
502 * The default value of the successful argument is true. If this value is false the value(s)
\r
503 * for each element is returned.
\r
505 * Note: This method *always* returns an array. If no valid value can be determined the
\r
506 * array will be empty, otherwise it will contain one or more values.
\r
508 $.fn.fieldValue = function(successful) {
\r
509 for (var val=[], i=0, max=this.length; i < max; i++) {
\r
511 var v = $.fieldValue(el, successful);
\r
512 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
\r
514 v.constructor == Array ? $.merge(val, v) : val.push(v);
\r
520 * Returns the value of the field element.
\r
522 $.fieldValue = function(el, successful) {
\r
523 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
\r
524 if (typeof successful == 'undefined') successful = true;
\r
526 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
\r
527 (t == 'checkbox' || t == 'radio') && !el.checked ||
\r
528 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
\r
529 tag == 'select' && el.selectedIndex == -1))
\r
532 if (tag == 'select') {
\r
533 var index = el.selectedIndex;
\r
534 if (index < 0) return null;
\r
535 var a = [], ops = el.options;
\r
536 var one = (t == 'select-one');
\r
537 var max = (one ? index+1 : ops.length);
\r
538 for(var i=(one ? index : 0); i < max; i++) {
\r
542 if (!v) // extra pain for IE...
\r
543 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
\r
554 * Clears the form data. Takes the following actions on the form's input fields:
\r
555 * - input text fields will have their 'value' property set to the empty string
\r
556 * - select elements will have their 'selectedIndex' property set to -1
\r
557 * - checkbox and radio inputs will have their 'checked' property set to false
\r
558 * - inputs of type submit, button, reset, and hidden will *not* be effected
\r
559 * - button elements will *not* be effected
\r
561 $.fn.clearForm = function() {
\r
562 return this.each(function() {
\r
563 $('input,select,textarea', this).clearFields();
\r
568 * Clears the selected form elements.
\r
570 $.fn.clearFields = $.fn.clearInputs = function() {
\r
571 return this.each(function() {
\r
572 var t = this.type, tag = this.tagName.toLowerCase();
\r
573 if (t == 'text' || t == 'password' || tag == 'textarea')
\r
575 else if (t == 'checkbox' || t == 'radio')
\r
576 this.checked = false;
\r
577 else if (tag == 'select')
\r
578 this.selectedIndex = -1;
\r
583 * Resets the form data. Causes all form elements to be reset to their original value.
\r
585 $.fn.resetForm = function() {
\r
586 return this.each(function() {
\r
587 // guard against an input with the name of 'reset'
\r
588 // note that IE reports the reset function as an 'object'
\r
589 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
\r
595 * Enables or disables any matching elements.
\r
597 $.fn.enable = function(b) {
\r
598 if (b == undefined) b = true;
\r
599 return this.each(function() {
\r
600 this.disabled = !b
\r
605 * Checks/unchecks any matching checkboxes or radio buttons and
\r
606 * selects/deselects and matching option elements.
\r
608 $.fn.selected = function(select) {
\r
609 if (select == undefined) select = true;
\r
610 return this.each(function() {
\r
612 if (t == 'checkbox' || t == 'radio')
\r
613 this.checked = select;
\r
614 else if (this.tagName.toLowerCase() == 'option') {
\r
615 var $sel = $(this).parent('select');
\r
616 if (select && $sel[0] && $sel[0].type == 'select-one') {
\r
617 // deselect all other options
\r
618 $sel.find('option').selected(false);
\r
620 this.selected = select;
\r
625 // helper fn for console logging
\r
626 // set $.fn.ajaxSubmit.debug to true to enable debug logging
\r
628 if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
\r
629 window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
\r