blob: b19fa955f186221481fa797ec12c0517462727cc [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001/*!
2 * jQuery Form Plugin
3 * version: 2.43 (12-MAR-2010)
4 * @requires jQuery v1.3.2 or later
5 *
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
10 */
11;(function($) {
12
13/*
14 Usage Note:
15 -----------
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,
19
20 $(document).ready(function() {
21 $('#myForm').bind('submit', function() {
22 $(this).ajaxSubmit({
23 target: '#output'
24 });
25 return false; // <-- important!
26 });
27 });
28
29 Use ajaxForm when you want the plugin to manage all the event binding
30 for you. For example,
31
32 $(document).ready(function() {
33 $('#myForm').ajaxForm({
34 target: '#output'
35 });
36 });
37
38 When using ajaxForm, the ajaxSubmit function will be invoked for you
39 at the appropriate time.
40*/
41
42/**
43 * ajaxSubmit() provides a mechanism for immediately submitting
44 * an HTML form using AJAX.
45 */
46$.fn.ajaxSubmit = function(options) {
47 // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
48 if (!this.length) {
49 log('ajaxSubmit: skipping submit process - no element selected');
50 return this;
51 }
52
53 if (typeof options == 'function')
54 options = { success: options };
55
56 var url = $.trim(this.attr('action'));
57 if (url) {
58 // clean url (don't include hash vaue)
59 url = (url.match(/^([^#]+)/)||[])[1];
60 }
61 url = url || window.location.href || '';
62
63 options = $.extend({
64 url: url,
65 type: this.attr('method') || 'GET',
66 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
67 }, options || {});
68
69 // hook for manipulating the form data before it is extracted;
70 // convenient for use with rich editors like tinyMCE or FCKEditor
71 var veto = {};
72 this.trigger('form-pre-serialize', [this, options, veto]);
73 if (veto.veto) {
74 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
75 return this;
76 }
77
78 // provide opportunity to alter form data before it is serialized
79 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
80 log('ajaxSubmit: submit aborted via beforeSerialize callback');
81 return this;
82 }
83
84 var a = this.formToArray(options.semantic);
85 if (options.data) {
86 options.extraData = options.data;
87 for (var n in options.data) {
88 if(options.data[n] instanceof Array) {
89 for (var k in options.data[n])
90 a.push( { name: n, value: options.data[n][k] } );
91 }
92 else
93 a.push( { name: n, value: options.data[n] } );
94 }
95 }
96
97 // give pre-submit callback an opportunity to abort the submit
98 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
99 log('ajaxSubmit: submit aborted via beforeSubmit callback');
100 return this;
101 }
102
103 // fire vetoable 'validate' event
104 this.trigger('form-submit-validate', [a, this, options, veto]);
105 if (veto.veto) {
106 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
107 return this;
108 }
109
110 var q = $.param(a);
111
112 if (options.type.toUpperCase() == 'GET') {
113 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
114 options.data = null; // data is null for 'get'
115 }
116 else
117 options.data = q; // data is the query string for 'post'
118
119 var $form = this, callbacks = [];
120 if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
121 if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
122
123 // perform a load on the target only if dataType is not provided
124 if (!options.dataType && options.target) {
125 var oldSuccess = options.success || function(){};
126 callbacks.push(function(data) {
127 var fn = options.replaceTarget ? 'replaceWith' : 'html';
128 $(options.target)[fn](data).each(oldSuccess, arguments);
129 });
130 }
131 else if (options.success)
132 callbacks.push(options.success);
133
134 options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
135 for (var i=0, max=callbacks.length; i < max; i++)
136 callbacks[i].apply(options, [data, status, xhr || $form, $form]);
137 };
138
139 // are there files to upload?
140 var files = $('input:file', this).fieldValue();
141 var found = false;
142 for (var j=0; j < files.length; j++)
143 if (files[j])
144 found = true;
145
146 var multipart = false;
147// var mp = 'multipart/form-data';
148// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
149
150 // options.iframe allows user to force iframe mode
151 // 06-NOV-09: now defaulting to iframe mode if file input is detected
152 if ((files.length && options.iframe !== false) || options.iframe || found || multipart) {
153 // hack to fix Safari hang (thanks to Tim Molendijk for this)
154 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
155 if (options.closeKeepAlive)
156 $.get(options.closeKeepAlive, fileUpload);
157 else
158 fileUpload();
159 }
160 else
161 $.ajax(options);
162
163 // fire 'notify' event
164 this.trigger('form-submit-notify', [this, options]);
165 return this;
166
167
168 // private function for handling file uploads (hat tip to YAHOO!)
169 function fileUpload() {
170 var form = $form[0];
171
172 if ($(':input[name=submit]', form).length) {
173 alert('Error: Form elements must not be named "submit".');
174 return;
175 }
176
177 var opts = $.extend({}, $.ajaxSettings, options);
178 var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
179
180 var id = 'jqFormIO' + (new Date().getTime());
181 var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ opts.iframeSrc +'" onload="(jQuery(this).data(\'form-plugin-onload\'))()" />');
182 var io = $io[0];
183
184 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
185
186 var xhr = { // mock object
187 aborted: 0,
188 responseText: null,
189 responseXML: null,
190 status: 0,
191 async: false,
192 statusText: 'n/a',
193 getAllResponseHeaders: function() {},
194 getResponseHeader: function() {},
195 setRequestHeader: function() {},
196 abort: function() {
197 this.aborted = 1;
198 $io.attr('src', opts.iframeSrc); // abort op in progress
199 }
200 };
201
202 var g = opts.global;
203 // trigger ajax global events so that activity/block indicators work like normal
204 if (g && ! $.active++) $.event.trigger("ajaxStart");
205 if (g) $.event.trigger("ajaxSend", [xhr, opts]);
206
207 if (s.beforeSend && s.beforeSend(xhr, s) === false) {
208 s.global && $.active--;
209 return;
210 }
211 if (xhr.aborted)
212 return;
213
214 var cbInvoked = false;
215 var timedOut = 0;
216
217 // add submitting element to data if we know it
218 var sub = form.clk;
219 if (sub) {
220 var n = sub.name;
221 if (n && !sub.disabled) {
222 opts.extraData = opts.extraData || {};
223 opts.extraData[n] = sub.value;
224 if (sub.type == "image") {
225 opts.extraData[n+'.x'] = form.clk_x;
226 opts.extraData[n+'.y'] = form.clk_y;
227 }
228 }
229 }
230
231 // take a breath so that pending repaints get some cpu time before the upload starts
232 function doSubmit() {
233 // make sure form attrs are set
234 var t = $form.attr('target'), a = $form.attr('action');
235
236 // update form attrs in IE friendly way
237 form.setAttribute('target',id);
238 if (form.getAttribute('method') != 'POST')
239 form.setAttribute('method', 'POST');
240 if (form.getAttribute('action') != opts.url)
241 form.setAttribute('action', opts.url);
242
243 // ie borks in some cases when setting encoding
244 if (! opts.skipEncodingOverride) {
245 $form.attr({
246 encoding: 'multipart/form-data',
247 enctype: 'multipart/form-data'
248 });
249 }
250
251 // support timout
252 if (opts.timeout)
253 setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
254
255 // add "extra" data to form if provided in options
256 var extraInputs = [];
257 try {
258 if (opts.extraData)
259 for (var n in opts.extraData)
260 extraInputs.push(
261 $('<input type="hidden" name="'+n+'" value="'+opts.extraData[n]+'" />')
262 .appendTo(form)[0]);
263
264 // add iframe to doc and submit the form
265 $io.appendTo('body');
266 $io.data('form-plugin-onload', cb);
267 form.submit();
268 }
269 finally {
270 // reset attrs and remove "extra" input elements
271 form.setAttribute('action',a);
272 t ? form.setAttribute('target', t) : $form.removeAttr('target');
273 $(extraInputs).remove();
274 }
275 };
276
277 if (opts.forceSync)
278 doSubmit();
279 else
280 setTimeout(doSubmit, 10); // this lets dom updates render
281
282 var domCheckCount = 100;
283
284 function cb() {
285 if (cbInvoked)
286 return;
287
288 var ok = true;
289 try {
290 if (timedOut) throw 'timeout';
291 // extract the server response from the iframe
292 var data, doc;
293
294 doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
295
296 var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
297 log('isXml='+isXml);
298 if (!isXml && (doc.body == null || doc.body.innerHTML == '')) {
299 if (--domCheckCount) {
300 // in some browsers (Opera) the iframe DOM is not always traversable when
301 // the onload callback fires, so we loop a bit to accommodate
302 log('requeing onLoad callback, DOM not available');
303 setTimeout(cb, 250);
304 return;
305 }
306 log('Could not access iframe DOM after 100 tries.');
307 return;
308 }
309
310 log('response detected');
311 cbInvoked = true;
312 xhr.responseText = doc.body ? doc.body.innerHTML : null;
313 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
314 xhr.getResponseHeader = function(header){
315 var headers = {'content-type': opts.dataType};
316 return headers[header];
317 };
318
319 if (opts.dataType == 'json' || opts.dataType == 'script') {
320 // see if user embedded response in textarea
321 var ta = doc.getElementsByTagName('textarea')[0];
322 if (ta)
323 xhr.responseText = ta.value;
324 else {
325 // account for browsers injecting pre around json response
326 var pre = doc.getElementsByTagName('pre')[0];
327 if (pre)
328 xhr.responseText = pre.innerHTML;
329 }
330 }
331 else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
332 xhr.responseXML = toXml(xhr.responseText);
333 }
334 data = $.httpData(xhr, opts.dataType);
335 }
336 catch(e){
337 log('error caught:',e);
338 ok = false;
339 xhr.error = e;
340 $.handleError(opts, xhr, 'error', e);
341 }
342
343 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
344 if (ok) {
345 opts.success(data, 'success');
346 if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
347 }
348 if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
349 if (g && ! --$.active) $.event.trigger("ajaxStop");
350 if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
351
352 // clean up
353 setTimeout(function() {
354 $io.removeData('form-plugin-onload');
355 $io.remove();
356 xhr.responseXML = null;
357 }, 100);
358 };
359
360 function toXml(s, doc) {
361 if (window.ActiveXObject) {
362 doc = new ActiveXObject('Microsoft.XMLDOM');
363 doc.async = 'false';
364 doc.loadXML(s);
365 }
366 else
367 doc = (new DOMParser()).parseFromString(s, 'text/xml');
368 return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
369 };
370 };
371};
372
373/**
374 * ajaxForm() provides a mechanism for fully automating form submission.
375 *
376 * The advantages of using this method instead of ajaxSubmit() are:
377 *
378 * 1: This method will include coordinates for <input type="image" /> elements (if the element
379 * is used to submit the form).
380 * 2. This method will include the submit element's name/value data (for the element that was
381 * used to submit the form).
382 * 3. This method binds the submit() method to the form for you.
383 *
384 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
385 * passes the options argument along after properly binding events for submit elements and
386 * the form itself.
387 */
388$.fn.ajaxForm = function(options) {
389 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
390 e.preventDefault();
391 $(this).ajaxSubmit(options);
392 }).bind('click.form-plugin', function(e) {
393 var target = e.target;
394 var $el = $(target);
395 if (!($el.is(":submit,input:image"))) {
396 // is this a child element of the submit el? (ex: a span within a button)
397 var t = $el.closest(':submit');
398 if (t.length == 0)
399 return;
400 target = t[0];
401 }
402 var form = this;
403 form.clk = target;
404 if (target.type == 'image') {
405 if (e.offsetX != undefined) {
406 form.clk_x = e.offsetX;
407 form.clk_y = e.offsetY;
408 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
409 var offset = $el.offset();
410 form.clk_x = e.pageX - offset.left;
411 form.clk_y = e.pageY - offset.top;
412 } else {
413 form.clk_x = e.pageX - target.offsetLeft;
414 form.clk_y = e.pageY - target.offsetTop;
415 }
416 }
417 // clear form vars
418 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
419 });
420};
421
422// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
423$.fn.ajaxFormUnbind = function() {
424 return this.unbind('submit.form-plugin click.form-plugin');
425};
426
427/**
428 * formToArray() gathers form element data into an array of objects that can
429 * be passed to any of the following ajax functions: $.get, $.post, or load.
430 * Each object in the array has both a 'name' and 'value' property. An example of
431 * an array for a simple login form might be:
432 *
433 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
434 *
435 * It is this array that is passed to pre-submit callback functions provided to the
436 * ajaxSubmit() and ajaxForm() methods.
437 */
438$.fn.formToArray = function(semantic) {
439 var a = [];
440 if (this.length == 0) return a;
441
442 var form = this[0];
443 var els = semantic ? form.getElementsByTagName('*') : form.elements;
444 if (!els) return a;
445 for(var i=0, max=els.length; i < max; i++) {
446 var el = els[i];
447 var n = el.name;
448 if (!n) continue;
449
450 if (semantic && form.clk && el.type == "image") {
451 // handle image inputs on the fly when semantic == true
452 if(!el.disabled && form.clk == el) {
453 a.push({name: n, value: $(el).val()});
454 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
455 }
456 continue;
457 }
458
459 var v = $.fieldValue(el, true);
460 if (v && v.constructor == Array) {
461 for(var j=0, jmax=v.length; j < jmax; j++)
462 a.push({name: n, value: v[j]});
463 }
464 else if (v !== null && typeof v != 'undefined')
465 a.push({name: n, value: v});
466 }
467
468 if (!semantic && form.clk) {
469 // input type=='image' are not found in elements array! handle it here
470 var $input = $(form.clk), input = $input[0], n = input.name;
471 if (n && !input.disabled && input.type == 'image') {
472 a.push({name: n, value: $input.val()});
473 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
474 }
475 }
476 return a;
477};
478
479/**
480 * Serializes form data into a 'submittable' string. This method will return a string
481 * in the format: name1=value1&amp;name2=value2
482 */
483$.fn.formSerialize = function(semantic) {
484 //hand off to jQuery.param for proper encoding
485 return $.param(this.formToArray(semantic));
486};
487
488/**
489 * Serializes all field elements in the jQuery object into a query string.
490 * This method will return a string in the format: name1=value1&amp;name2=value2
491 */
492$.fn.fieldSerialize = function(successful) {
493 var a = [];
494 this.each(function() {
495 var n = this.name;
496 if (!n) return;
497 var v = $.fieldValue(this, successful);
498 if (v && v.constructor == Array) {
499 for (var i=0,max=v.length; i < max; i++)
500 a.push({name: n, value: v[i]});
501 }
502 else if (v !== null && typeof v != 'undefined')
503 a.push({name: this.name, value: v});
504 });
505 //hand off to jQuery.param for proper encoding
506 return $.param(a);
507};
508
509/**
510 * Returns the value(s) of the element in the matched set. For example, consider the following form:
511 *
512 * <form><fieldset>
513 * <input name="A" type="text" />
514 * <input name="A" type="text" />
515 * <input name="B" type="checkbox" value="B1" />
516 * <input name="B" type="checkbox" value="B2"/>
517 * <input name="C" type="radio" value="C1" />
518 * <input name="C" type="radio" value="C2" />
519 * </fieldset></form>
520 *
521 * var v = $(':text').fieldValue();
522 * // if no values are entered into the text inputs
523 * v == ['','']
524 * // if values entered into the text inputs are 'foo' and 'bar'
525 * v == ['foo','bar']
526 *
527 * var v = $(':checkbox').fieldValue();
528 * // if neither checkbox is checked
529 * v === undefined
530 * // if both checkboxes are checked
531 * v == ['B1', 'B2']
532 *
533 * var v = $(':radio').fieldValue();
534 * // if neither radio is checked
535 * v === undefined
536 * // if first radio is checked
537 * v == ['C1']
538 *
539 * The successful argument controls whether or not the field element must be 'successful'
540 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
541 * The default value of the successful argument is true. If this value is false the value(s)
542 * for each element is returned.
543 *
544 * Note: This method *always* returns an array. If no valid value can be determined the
545 * array will be empty, otherwise it will contain one or more values.
546 */
547$.fn.fieldValue = function(successful) {
548 for (var val=[], i=0, max=this.length; i < max; i++) {
549 var el = this[i];
550 var v = $.fieldValue(el, successful);
551 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
552 continue;
553 v.constructor == Array ? $.merge(val, v) : val.push(v);
554 }
555 return val;
556};
557
558/**
559 * Returns the value of the field element.
560 */
561$.fieldValue = function(el, successful) {
562 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
563 if (typeof successful == 'undefined') successful = true;
564
565 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
566 (t == 'checkbox' || t == 'radio') && !el.checked ||
567 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
568 tag == 'select' && el.selectedIndex == -1))
569 return null;
570
571 if (tag == 'select') {
572 var index = el.selectedIndex;
573 if (index < 0) return null;
574 var a = [], ops = el.options;
575 var one = (t == 'select-one');
576 var max = (one ? index+1 : ops.length);
577 for(var i=(one ? index : 0); i < max; i++) {
578 var op = ops[i];
579 if (op.selected) {
580 var v = op.value;
581 if (!v) // extra pain for IE...
582 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
583 if (one) return v;
584 a.push(v);
585 }
586 }
587 return a;
588 }
589 return el.value;
590};
591
592/**
593 * Clears the form data. Takes the following actions on the form's input fields:
594 * - input text fields will have their 'value' property set to the empty string
595 * - select elements will have their 'selectedIndex' property set to -1
596 * - checkbox and radio inputs will have their 'checked' property set to false
597 * - inputs of type submit, button, reset, and hidden will *not* be effected
598 * - button elements will *not* be effected
599 */
600$.fn.clearForm = function() {
601 return this.each(function() {
602 $('input,select,textarea', this).clearFields();
603 });
604};
605
606/**
607 * Clears the selected form elements.
608 */
609$.fn.clearFields = $.fn.clearInputs = function() {
610 return this.each(function() {
611 var t = this.type, tag = this.tagName.toLowerCase();
612 if (t == 'text' || t == 'password' || tag == 'textarea')
613 this.value = '';
614 else if (t == 'checkbox' || t == 'radio')
615 this.checked = false;
616 else if (tag == 'select')
617 this.selectedIndex = -1;
618 });
619};
620
621/**
622 * Resets the form data. Causes all form elements to be reset to their original value.
623 */
624$.fn.resetForm = function() {
625 return this.each(function() {
626 // guard against an input with the name of 'reset'
627 // note that IE reports the reset function as an 'object'
628 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
629 this.reset();
630 });
631};
632
633/**
634 * Enables or disables any matching elements.
635 */
636$.fn.enable = function(b) {
637 if (b == undefined) b = true;
638 return this.each(function() {
639 this.disabled = !b;
640 });
641};
642
643/**
644 * Checks/unchecks any matching checkboxes or radio buttons and
645 * selects/deselects and matching option elements.
646 */
647$.fn.selected = function(select) {
648 if (select == undefined) select = true;
649 return this.each(function() {
650 var t = this.type;
651 if (t == 'checkbox' || t == 'radio')
652 this.checked = select;
653 else if (this.tagName.toLowerCase() == 'option') {
654 var $sel = $(this).parent('select');
655 if (select && $sel[0] && $sel[0].type == 'select-one') {
656 // deselect all other options
657 $sel.find('option').selected(false);
658 }
659 this.selected = select;
660 }
661 });
662};
663
664// helper fn for console logging
665// set $.fn.ajaxSubmit.debug to true to enable debug logging
666function log() {
667 if ($.fn.ajaxSubmit.debug) {
668 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
669 if (window.console && window.console.log)
670 window.console.log(msg);
671 else if (window.opera && window.opera.postError)
672 window.opera.postError(msg);
673 }
674};
675
676})(jQuery);