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