(bug 36991) Fix jquery.tablesorter date sorting
[mediawiki.git] / resources / jquery / jquery.form.js
blobfdcdd15ac3809b2e970034d92560b45ffd2e6cd2
1 /*!
2  * jQuery Form Plugin
3  * version: 2.84 (12-AUG-2011)
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($) {
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,
20         $(document).ready(function() {
21                 $('#myForm').bind('submit', function(e) {
22                         e.preventDefault(); // <-- important
23                         $(this).ajaxSubmit({
24                                 target: '#output'
25                         });
26                 });
27         });
29         Use ajaxForm when you want the plugin to manage all the event binding
30         for you.  For example,
32         $(document).ready(function() {
33                 $('#myForm').ajaxForm({
34                         target: '#output'
35                 });
36         });
38         When using ajaxForm, the ajaxSubmit function will be invoked for you
39         at the appropriate time.
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         var method, action, url, $form = this;
55         if (typeof options == 'function') {
56                 options = { success: options };
57         }
59         method = this.attr('method');
60         action = this.attr('action');
61         url = (typeof action === 'string') ? $.trim(action) : '';
62         url = url || window.location.href || '';
63         if (url) {
64                 // clean url (don't include hash vaue)
65                 url = (url.match(/^([^#]+)/)||[])[1];
66         }
68         options = $.extend(true, {
69                 url:  url,
70                 success: $.ajaxSettings.success,
71                 type: method || 'GET',
72                 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
73         }, options);
75         // hook for manipulating the form data before it is extracted;
76         // convenient for use with rich editors like tinyMCE or FCKEditor
77         var veto = {};
78         this.trigger('form-pre-serialize', [this, options, veto]);
79         if (veto.veto) {
80                 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
81                 return this;
82         }
84         // provide opportunity to alter form data before it is serialized
85         if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
86                 log('ajaxSubmit: submit aborted via beforeSerialize callback');
87                 return this;
88         }
90         var n,v,a = this.formToArray(options.semantic);
91         if (options.data) {
92                 options.extraData = options.data;
93                 for (n in options.data) {
94                         if( $.isArray(options.data[n]) ) {
95                                 for (var k in options.data[n]) {
96                                         a.push( { name: n, value: options.data[n][k] } );
97                                 }
98                         }
99                         else {
100                                 v = options.data[n];
101                                 v = $.isFunction(v) ? v() : v; // if value is fn, invoke it
102                                 a.push( { name: n, value: v } );
103                         }
104                 }
105         }
107         // give pre-submit callback an opportunity to abort the submit
108         if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
109                 log('ajaxSubmit: submit aborted via beforeSubmit callback');
110                 return this;
111         }
113         // fire vetoable 'validate' event
114         this.trigger('form-submit-validate', [a, this, options, veto]);
115         if (veto.veto) {
116                 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
117                 return this;
118         }
120         var q = $.param(a);
122         if (options.type.toUpperCase() == 'GET') {
123                 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
124                 options.data = null;  // data is null for 'get'
125         }
126         else {
127                 options.data = q; // data is the query string for 'post'
128         }
130         var callbacks = [];
131         if (options.resetForm) {
132                 callbacks.push(function() { $form.resetForm(); });
133         }
134         if (options.clearForm) {
135                 callbacks.push(function() { $form.clearForm(); });
136         }
138         // perform a load on the target only if dataType is not provided
139         if (!options.dataType && options.target) {
140                 var oldSuccess = options.success || function(){};
141                 callbacks.push(function(data) {
142                         var fn = options.replaceTarget ? 'replaceWith' : 'html';
143                         $(options.target)[fn](data).each(oldSuccess, arguments);
144                 });
145         }
146         else if (options.success) {
147                 callbacks.push(options.success);
148         }
150         options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
151                 var context = options.context || options;   // jQuery 1.4+ supports scope context 
152                 for (var i=0, max=callbacks.length; i < max; i++) {
153                         callbacks[i].apply(context, [data, status, xhr || $form, $form]);
154                 }
155         };
157         // are there files to upload?
158         var fileInputs = $('input:file', this).length > 0;
159         var mp = 'multipart/form-data';
160         var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
162         // options.iframe allows user to force iframe mode
163         // 06-NOV-09: now defaulting to iframe mode if file input is detected
164    if (options.iframe !== false && (fileInputs || options.iframe || multipart)) {
165            // hack to fix Safari hang (thanks to Tim Molendijk for this)
166            // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
167            if (options.closeKeepAlive) {
168                    $.get(options.closeKeepAlive, function() { fileUpload(a); });
169                 }
170            else {
171                    fileUpload(a);
172                 }
173    }
174    else {
175                 // IE7 massage (see issue 57)
176                 if ($.browser.msie && method == 'get') { 
177                         var ieMeth = $form[0].getAttribute('method');
178                         if (typeof ieMeth === 'string')
179                                 options.type = ieMeth;
180                 }
181                 $.ajax(options);
182    }
184         // fire 'notify' event
185         this.trigger('form-submit-notify', [this, options]);
186         return this;
189         // private function for handling file uploads (hat tip to YAHOO!)
190         function fileUpload(a) {
191                 var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
192         var useProp = !!$.fn.prop;
194         if (a) {
195                 // ensure that every serialized input is still enabled
196                 for (i=0; i < a.length; i++) {
197                 el = $(form[a[i].name]);
198                 el[ useProp ? 'prop' : 'attr' ]('disabled', false);
199                 }
200         }
202                 if ($(':input[name=submit],:input[id=submit]', form).length) {
203                         // if there is an input with a name or id of 'submit' then we won't be
204                         // able to invoke the submit fn on the form (at least not x-browser)
205                         alert('Error: Form elements must not have name or id of "submit".');
206                         return;
207                 }
208                 
209                 s = $.extend(true, {}, $.ajaxSettings, options);
210                 s.context = s.context || s;
211                 id = 'jqFormIO' + (new Date().getTime());
212                 if (s.iframeTarget) {
213                         $io = $(s.iframeTarget);
214                         n = $io.attr('name');
215                         if (n == null)
216                                 $io.attr('name', id);
217                         else
218                                 id = n;
219                 }
220                 else {
221                         $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
222                         $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
223                 }
224                 io = $io[0];
227                 xhr = { // mock object
228                         aborted: 0,
229                         responseText: null,
230                         responseXML: null,
231                         status: 0,
232                         statusText: 'n/a',
233                         getAllResponseHeaders: function() {},
234                         getResponseHeader: function() {},
235                         setRequestHeader: function() {},
236                         abort: function(status) {
237                                 var e = (status === 'timeout' ? 'timeout' : 'aborted');
238                                 log('aborting upload... ' + e);
239                                 this.aborted = 1;
240                                 $io.attr('src', s.iframeSrc); // abort op in progress
241                                 xhr.error = e;
242                                 s.error && s.error.call(s.context, xhr, e, status);
243                                 g && $.event.trigger("ajaxError", [xhr, s, e]);
244                                 s.complete && s.complete.call(s.context, xhr, e);
245                         }
246                 };
248                 g = s.global;
249                 // trigger ajax global events so that activity/block indicators work like normal
250                 if (g && ! $.active++) {
251                         $.event.trigger("ajaxStart");
252                 }
253                 if (g) {
254                         $.event.trigger("ajaxSend", [xhr, s]);
255                 }
257                 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
258                         if (s.global) {
259                                 $.active--;
260                         }
261                         return;
262                 }
263                 if (xhr.aborted) {
264                         return;
265                 }
267                 // add submitting element to data if we know it
268                 sub = form.clk;
269                 if (sub) {
270                         n = sub.name;
271                         if (n && !sub.disabled) {
272                                 s.extraData = s.extraData || {};
273                                 s.extraData[n] = sub.value;
274                                 if (sub.type == "image") {
275                                         s.extraData[n+'.x'] = form.clk_x;
276                                         s.extraData[n+'.y'] = form.clk_y;
277                                 }
278                         }
279                 }
280                 
281                 var CLIENT_TIMEOUT_ABORT = 1;
282                 var SERVER_ABORT = 2;
284                 function getDoc(frame) {
285                         var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
286                         return doc;
287                 }
288                 
289                 // take a breath so that pending repaints get some cpu time before the upload starts
290                 function doSubmit() {
291                         // make sure form attrs are set
292                         var t = $form.attr('target'), a = $form.attr('action');
294                         // update form attrs in IE friendly way
295                         form.setAttribute('target',id);
296                         if (!method) {
297                                 form.setAttribute('method', 'POST');
298                         }
299                         if (a != s.url) {
300                                 form.setAttribute('action', s.url);
301                         }
303                         // ie borks in some cases when setting encoding
304                         if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
305                                 $form.attr({
306                                         encoding: 'multipart/form-data',
307                                         enctype:  'multipart/form-data'
308                                 });
309                         }
311                         // support timout
312                         if (s.timeout) {
313                                 timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
314                         }
315                         
316                         // look for server aborts
317                         function checkState() {
318                                 try {
319                                         var state = getDoc(io).readyState;
320                                         log('state = ' + state);
321                                         if (state.toLowerCase() == 'uninitialized')
322                                                 setTimeout(checkState,50);
323                                 }
324                                 catch(e) {
325                                         log('Server abort: ' , e, ' (', e.name, ')');
326                                         cb(SERVER_ABORT);
327                                         timeoutHandle && clearTimeout(timeoutHandle);
328                                         timeoutHandle = undefined;
329                                 }
330                         }
332                         // add "extra" data to form if provided in options
333                         var extraInputs = [];
334                         try {
335                                 if (s.extraData) {
336                                         for (var n in s.extraData) {
337                                                 extraInputs.push(
338                                                         $('<input type="hidden" name="'+n+'" />').attr('value',s.extraData[n])
339                                                                 .appendTo(form)[0]);
340                                         }
341                                 }
343                                 if (!s.iframeTarget) {
344                                         // add iframe to doc and submit the form
345                                         $io.appendTo('body');
346                         io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
347                                 }
348                                 setTimeout(checkState,15);
349                                 form.submit();
350                         }
351                         finally {
352                                 // reset attrs and remove "extra" input elements
353                                 form.setAttribute('action',a);
354                                 if(t) {
355                                         form.setAttribute('target', t);
356                                 } else {
357                                         $form.removeAttr('target');
358                                 }
359                                 $(extraInputs).remove();
360                         }
361                 }
363                 if (s.forceSync) {
364                         doSubmit();
365                 }
366                 else {
367                         setTimeout(doSubmit, 10); // this lets dom updates render
368                 }
370                 var data, doc, domCheckCount = 50, callbackProcessed;
372                 function cb(e) {
373                         if (xhr.aborted || callbackProcessed) {
374                                 return;
375                         }
376                         try {
377                                 doc = getDoc(io);
378                         }
379                         catch(ex) {
380                                 log('cannot access response document: ', ex);
381                                 e = SERVER_ABORT;
382                         }
383                         if (e === CLIENT_TIMEOUT_ABORT && xhr) {
384                                 xhr.abort('timeout');
385                                 return;
386                         }
387                         else if (e == SERVER_ABORT && xhr) {
388                                 xhr.abort('server abort');
389                                 return;
390                         }
392                         if (!doc || doc.location.href == s.iframeSrc) {
393                                 // response not received yet
394                                 if (!timedOut)
395                                         return;
396                         }
397             io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
399                         var status = 'success', errMsg;
400                         try {
401                                 if (timedOut) {
402                                         throw 'timeout';
403                                 }
405                                 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
406                                 log('isXml='+isXml);
407                                 if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
408                                         if (--domCheckCount) {
409                                                 // in some browsers (Opera) the iframe DOM is not always traversable when
410                                                 // the onload callback fires, so we loop a bit to accommodate
411                                                 log('requeing onLoad callback, DOM not available');
412                                                 setTimeout(cb, 250);
413                                                 return;
414                                         }
415                                         // let this fall through because server response could be an empty document
416                                         //log('Could not access iframe DOM after mutiple tries.');
417                                         //throw 'DOMException: not available';
418                                 }
420                                 //log('response detected');
421                 var docRoot = doc.body ? doc.body : doc.documentElement;
422                 xhr.responseText = docRoot ? docRoot.innerHTML : null;
423                                 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
424                                 if (isXml)
425                                         s.dataType = 'xml';
426                                 xhr.getResponseHeader = function(header){
427                                         var headers = {'content-type': s.dataType};
428                                         return headers[header];
429                                 };
430                 // support for XHR 'status' & 'statusText' emulation :
431                 if (docRoot) {
432                     xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
433                     xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
434                 }
436                                 var dt = s.dataType || '';
437                                 var scr = /(json|script|text)/.test(dt.toLowerCase());
438                                 if (scr || s.textarea) {
439                                         // see if user embedded response in textarea
440                                         var ta = doc.getElementsByTagName('textarea')[0];
441                                         if (ta) {
442                                                 xhr.responseText = ta.value;
443                         // support for XHR 'status' & 'statusText' emulation :
444                         xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
445                         xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
446                                         }
447                                         else if (scr) {
448                                                 // account for browsers injecting pre around json response
449                                                 var pre = doc.getElementsByTagName('pre')[0];
450                                                 var b = doc.getElementsByTagName('body')[0];
451                                                 if (pre) {
452                                                         xhr.responseText = pre.textContent ? pre.textContent : pre.innerHTML;
453                                                 }
454                                                 else if (b) {
455                                                         xhr.responseText = b.innerHTML;
456                                                 }
457                                         }
458                                 }
459                                 else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
460                                         xhr.responseXML = toXml(xhr.responseText);
461                                 }
463                 try {
464                     data = httpData(xhr, s.dataType, s);
465                 }
466                 catch (e) {
467                     status = 'parsererror';
468                     xhr.error = errMsg = (e || status);
469                 }
470                         }
471                         catch (e) {
472                                 log('error caught: ',e);
473                                 status = 'error';
474                 xhr.error = errMsg = (e || status);
475                         }
477                         if (xhr.aborted) {
478                                 log('upload aborted');
479                                 status = null;
480                         }
482             if (xhr.status) { // we've set xhr.status
483                 status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
484             }
486                         // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
487                         if (status === 'success') {
488                                 s.success && s.success.call(s.context, data, 'success', xhr);
489                                 g && $.event.trigger("ajaxSuccess", [xhr, s]);
490                         }
491             else if (status) {
492                                 if (errMsg == undefined)
493                                         errMsg = xhr.statusText;
494                                 s.error && s.error.call(s.context, xhr, status, errMsg);
495                                 g && $.event.trigger("ajaxError", [xhr, s, errMsg]);
496             }
498                         g && $.event.trigger("ajaxComplete", [xhr, s]);
500                         if (g && ! --$.active) {
501                                 $.event.trigger("ajaxStop");
502                         }
504                         s.complete && s.complete.call(s.context, xhr, status);
506                         callbackProcessed = true;
507                         if (s.timeout)
508                                 clearTimeout(timeoutHandle);
510                         // clean up
511                         setTimeout(function() {
512                                 if (!s.iframeTarget)
513                                         $io.remove();
514                                 xhr.responseXML = null;
515                         }, 100);
516                 }
518                 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
519                         if (window.ActiveXObject) {
520                                 doc = new ActiveXObject('Microsoft.XMLDOM');
521                                 doc.async = 'false';
522                                 doc.loadXML(s);
523                         }
524                         else {
525                                 doc = (new DOMParser()).parseFromString(s, 'text/xml');
526                         }
527                         return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
528                 };
529                 var parseJSON = $.parseJSON || function(s) {
530                         return window['eval']('(' + s + ')');
531                 };
533                 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
535                         var ct = xhr.getResponseHeader('content-type') || '',
536                                 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
537                                 data = xml ? xhr.responseXML : xhr.responseText;
539                         if (xml && data.documentElement.nodeName === 'parsererror') {
540                                 $.error && $.error('parsererror');
541                         }
542                         if (s && s.dataFilter) {
543                                 data = s.dataFilter(data, type);
544                         }
545                         if (typeof data === 'string') {
546                                 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
547                                         data = parseJSON(data);
548                                 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
549                                         $.globalEval(data);
550                                 }
551                         }
552                         return data;
553                 };
554         }
558  * ajaxForm() provides a mechanism for fully automating form submission.
560  * The advantages of using this method instead of ajaxSubmit() are:
562  * 1: This method will include coordinates for <input type="image" /> elements (if the element
563  *      is used to submit the form).
564  * 2. This method will include the submit element's name/value data (for the element that was
565  *      used to submit the form).
566  * 3. This method binds the submit() method to the form for you.
568  * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
569  * passes the options argument along after properly binding events for submit elements and
570  * the form itself.
571  */
572 $.fn.ajaxForm = function(options) {
573         // in jQuery 1.3+ we can fix mistakes with the ready state
574         if (this.length === 0) {
575                 var o = { s: this.selector, c: this.context };
576                 if (!$.isReady && o.s) {
577                         log('DOM not ready, queuing ajaxForm');
578                         $(function() {
579                                 $(o.s,o.c).ajaxForm(options);
580                         });
581                         return this;
582                 }
583                 // is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
584                 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
585                 return this;
586         }
588         return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
589                 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
590                         e.preventDefault();
591                         $(this).ajaxSubmit(options);
592                 }
593         }).bind('click.form-plugin', function(e) {
594                 var target = e.target;
595                 var $el = $(target);
596                 if (!($el.is(":submit,input:image"))) {
597                         // is this a child element of the submit el?  (ex: a span within a button)
598                         var t = $el.closest(':submit');
599                         if (t.length == 0) {
600                                 return;
601                         }
602                         target = t[0];
603                 }
604                 var form = this;
605                 form.clk = target;
606                 if (target.type == 'image') {
607                         if (e.offsetX != undefined) {
608                                 form.clk_x = e.offsetX;
609                                 form.clk_y = e.offsetY;
610                         } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
611                                 var offset = $el.offset();
612                                 form.clk_x = e.pageX - offset.left;
613                                 form.clk_y = e.pageY - offset.top;
614                         } else {
615                                 form.clk_x = e.pageX - target.offsetLeft;
616                                 form.clk_y = e.pageY - target.offsetTop;
617                         }
618                 }
619                 // clear form vars
620                 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
621         });
624 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
625 $.fn.ajaxFormUnbind = function() {
626         return this.unbind('submit.form-plugin click.form-plugin');
630  * formToArray() gathers form element data into an array of objects that can
631  * be passed to any of the following ajax functions: $.get, $.post, or load.
632  * Each object in the array has both a 'name' and 'value' property.  An example of
633  * an array for a simple login form might be:
635  * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
637  * It is this array that is passed to pre-submit callback functions provided to the
638  * ajaxSubmit() and ajaxForm() methods.
639  */
640 $.fn.formToArray = function(semantic) {
641         var a = [];
642         if (this.length === 0) {
643                 return a;
644         }
646         var form = this[0];
647         var els = semantic ? form.getElementsByTagName('*') : form.elements;
648         if (!els) {
649                 return a;
650         }
652         var i,j,n,v,el,max,jmax;
653         for(i=0, max=els.length; i < max; i++) {
654                 el = els[i];
655                 n = el.name;
656                 if (!n) {
657                         continue;
658                 }
660                 if (semantic && form.clk && el.type == "image") {
661                         // handle image inputs on the fly when semantic == true
662                         if(!el.disabled && form.clk == el) {
663                                 a.push({name: n, value: $(el).val()});
664                                 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
665                         }
666                         continue;
667                 }
669                 v = $.fieldValue(el, true);
670                 if (v && v.constructor == Array) {
671                         for(j=0, jmax=v.length; j < jmax; j++) {
672                                 a.push({name: n, value: v[j]});
673                         }
674                 }
675                 else if (v !== null && typeof v != 'undefined') {
676                         a.push({name: n, value: v});
677                 }
678         }
680         if (!semantic && form.clk) {
681                 // input type=='image' are not found in elements array! handle it here
682                 var $input = $(form.clk), input = $input[0];
683                 n = input.name;
684                 if (n && !input.disabled && input.type == 'image') {
685                         a.push({name: n, value: $input.val()});
686                         a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
687                 }
688         }
689         return a;
693  * Serializes form data into a 'submittable' string. This method will return a string
694  * in the format: name1=value1&amp;name2=value2
695  */
696 $.fn.formSerialize = function(semantic) {
697         //hand off to jQuery.param for proper encoding
698         return $.param(this.formToArray(semantic));
702  * Serializes all field elements in the jQuery object into a query string.
703  * This method will return a string in the format: name1=value1&amp;name2=value2
704  */
705 $.fn.fieldSerialize = function(successful) {
706         var a = [];
707         this.each(function() {
708                 var n = this.name;
709                 if (!n) {
710                         return;
711                 }
712                 var v = $.fieldValue(this, successful);
713                 if (v && v.constructor == Array) {
714                         for (var i=0,max=v.length; i < max; i++) {
715                                 a.push({name: n, value: v[i]});
716                         }
717                 }
718                 else if (v !== null && typeof v != 'undefined') {
719                         a.push({name: this.name, value: v});
720                 }
721         });
722         //hand off to jQuery.param for proper encoding
723         return $.param(a);
727  * Returns the value(s) of the element in the matched set.  For example, consider the following form:
729  *  <form><fieldset>
730  *        <input name="A" type="text" />
731  *        <input name="A" type="text" />
732  *        <input name="B" type="checkbox" value="B1" />
733  *        <input name="B" type="checkbox" value="B2"/>
734  *        <input name="C" type="radio" value="C1" />
735  *        <input name="C" type="radio" value="C2" />
736  *  </fieldset></form>
738  *  var v = $(':text').fieldValue();
739  *  // if no values are entered into the text inputs
740  *  v == ['','']
741  *  // if values entered into the text inputs are 'foo' and 'bar'
742  *  v == ['foo','bar']
744  *  var v = $(':checkbox').fieldValue();
745  *  // if neither checkbox is checked
746  *  v === undefined
747  *  // if both checkboxes are checked
748  *  v == ['B1', 'B2']
750  *  var v = $(':radio').fieldValue();
751  *  // if neither radio is checked
752  *  v === undefined
753  *  // if first radio is checked
754  *  v == ['C1']
756  * The successful argument controls whether or not the field element must be 'successful'
757  * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
758  * The default value of the successful argument is true.  If this value is false the value(s)
759  * for each element is returned.
761  * Note: This method *always* returns an array.  If no valid value can be determined the
762  *         array will be empty, otherwise it will contain one or more values.
763  */
764 $.fn.fieldValue = function(successful) {
765         for (var val=[], i=0, max=this.length; i < max; i++) {
766                 var el = this[i];
767                 var v = $.fieldValue(el, successful);
768                 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
769                         continue;
770                 }
771                 v.constructor == Array ? $.merge(val, v) : val.push(v);
772         }
773         return val;
777  * Returns the value of the field element.
778  */
779 $.fieldValue = function(el, successful) {
780         var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
781         if (successful === undefined) {
782                 successful = true;
783         }
785         if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
786                 (t == 'checkbox' || t == 'radio') && !el.checked ||
787                 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
788                 tag == 'select' && el.selectedIndex == -1)) {
789                         return null;
790         }
792         if (tag == 'select') {
793                 var index = el.selectedIndex;
794                 if (index < 0) {
795                         return null;
796                 }
797                 var a = [], ops = el.options;
798                 var one = (t == 'select-one');
799                 var max = (one ? index+1 : ops.length);
800                 for(var i=(one ? index : 0); i < max; i++) {
801                         var op = ops[i];
802                         if (op.selected) {
803                                 var v = op.value;
804                                 if (!v) { // extra pain for IE...
805                                         v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
806                                 }
807                                 if (one) {
808                                         return v;
809                                 }
810                                 a.push(v);
811                         }
812                 }
813                 return a;
814         }
815         return $(el).val();
819  * Clears the form data.  Takes the following actions on the form's input fields:
820  *  - input text fields will have their 'value' property set to the empty string
821  *  - select elements will have their 'selectedIndex' property set to -1
822  *  - checkbox and radio inputs will have their 'checked' property set to false
823  *  - inputs of type submit, button, reset, and hidden will *not* be effected
824  *  - button elements will *not* be effected
825  */
826 $.fn.clearForm = function() {
827         return this.each(function() {
828                 $('input,select,textarea', this).clearFields();
829         });
833  * Clears the selected form elements.
834  */
835 $.fn.clearFields = $.fn.clearInputs = function() {
836         var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
837         return this.each(function() {
838                 var t = this.type, tag = this.tagName.toLowerCase();
839                 if (re.test(t) || tag == 'textarea') {
840                         this.value = '';
841                 }
842                 else if (t == 'checkbox' || t == 'radio') {
843                         this.checked = false;
844                 }
845                 else if (tag == 'select') {
846                         this.selectedIndex = -1;
847                 }
848         });
852  * Resets the form data.  Causes all form elements to be reset to their original value.
853  */
854 $.fn.resetForm = function() {
855         return this.each(function() {
856                 // guard against an input with the name of 'reset'
857                 // note that IE reports the reset function as an 'object'
858                 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
859                         this.reset();
860                 }
861         });
865  * Enables or disables any matching elements.
866  */
867 $.fn.enable = function(b) {
868         if (b === undefined) {
869                 b = true;
870         }
871         return this.each(function() {
872                 this.disabled = !b;
873         });
877  * Checks/unchecks any matching checkboxes or radio buttons and
878  * selects/deselects and matching option elements.
879  */
880 $.fn.selected = function(select) {
881         if (select === undefined) {
882                 select = true;
883         }
884         return this.each(function() {
885                 var t = this.type;
886                 if (t == 'checkbox' || t == 'radio') {
887                         this.checked = select;
888                 }
889                 else if (this.tagName.toLowerCase() == 'option') {
890                         var $sel = $(this).parent('select');
891                         if (select && $sel[0] && $sel[0].type == 'select-one') {
892                                 // deselect all other options
893                                 $sel.find('option').selected(false);
894                         }
895                         this.selected = select;
896                 }
897         });
900 // helper fn for console logging
901 function log() {
902         var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
903         if (window.console && window.console.log) {
904                 window.console.log(msg);
905         }
906         else if (window.opera && window.opera.postError) {
907                 window.opera.postError(msg);
908         }
911 })(jQuery);