Merge "Add ss_active_users in SiteStats::isSane"
[mediawiki.git] / resources / jquery / jquery.form.js
blob13e9a55c5836c9ec43ea9bf77a4c1c01e17e183b
1 /*!
2  * jQuery Form Plugin
3  * version: 3.14 (30-JUL-2012)
4  * @requires jQuery v1.3.2 or later
5  *
6  * Examples and documentation at: http://malsup.com/jquery/form/
7  * Project repository: https://github.com/malsup/form
8  * Dual licensed under the MIT and GPL licenses:
9  *    http://malsup.github.com/mit-license.txt
10  *    http://malsup.github.com/gpl-license-v2.txt
11  */
12 /*global ActiveXObject alert */
13 ;(function($) {
14 "use strict";
17     Usage Note:
18     -----------
19     Do not use both ajaxSubmit and ajaxForm on the same form.  These
20     functions are mutually exclusive.  Use ajaxSubmit if you want
21     to bind your own submit handler to the form.  For example,
23     $(document).ready(function() {
24         $('#myForm').on('submit', function(e) {
25             e.preventDefault(); // <-- important
26             $(this).ajaxSubmit({
27                 target: '#output'
28             });
29         });
30     });
32     Use ajaxForm when you want the plugin to manage all the event binding
33     for you.  For example,
35     $(document).ready(function() {
36         $('#myForm').ajaxForm({
37             target: '#output'
38         });
39     });
40     
41     You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
42     form does not have to exist when you invoke ajaxForm:
44     $('#myForm').ajaxForm({
45         delegation: true,
46         target: '#output'
47     });
48     
49     When using ajaxForm, the ajaxSubmit function will be invoked for you
50     at the appropriate time.
53 /**
54  * Feature detection
55  */
56 var feature = {};
57 feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
58 feature.formdata = window.FormData !== undefined;
60 /**
61  * ajaxSubmit() provides a mechanism for immediately submitting
62  * an HTML form using AJAX.
63  */
64 $.fn.ajaxSubmit = function(options) {
65     /*jshint scripturl:true */
67     // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
68     if (!this.length) {
69         log('ajaxSubmit: skipping submit process - no element selected');
70         return this;
71     }
72     
73     var method, action, url, $form = this;
75     if (typeof options == 'function') {
76         options = { success: options };
77     }
79     method = this.attr('method');
80     action = this.attr('action');
81     url = (typeof action === 'string') ? $.trim(action) : '';
82     url = url || window.location.href || '';
83     if (url) {
84         // clean url (don't include hash vaue)
85         url = (url.match(/^([^#]+)/)||[])[1];
86     }
88     options = $.extend(true, {
89         url:  url,
90         success: $.ajaxSettings.success,
91         type: method || 'GET',
92         iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
93     }, options);
95     // hook for manipulating the form data before it is extracted;
96     // convenient for use with rich editors like tinyMCE or FCKEditor
97     var veto = {};
98     this.trigger('form-pre-serialize', [this, options, veto]);
99     if (veto.veto) {
100         log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
101         return this;
102     }
104     // provide opportunity to alter form data before it is serialized
105     if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
106         log('ajaxSubmit: submit aborted via beforeSerialize callback');
107         return this;
108     }
110     var traditional = options.traditional;
111     if ( traditional === undefined ) {
112         traditional = $.ajaxSettings.traditional;
113     }
114     
115     var elements = [];
116     var qx, a = this.formToArray(options.semantic, elements);
117     if (options.data) {
118         options.extraData = options.data;
119         qx = $.param(options.data, traditional);
120     }
122     // give pre-submit callback an opportunity to abort the submit
123     if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
124         log('ajaxSubmit: submit aborted via beforeSubmit callback');
125         return this;
126     }
128     // fire vetoable 'validate' event
129     this.trigger('form-submit-validate', [a, this, options, veto]);
130     if (veto.veto) {
131         log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
132         return this;
133     }
135     var q = $.param(a, traditional);
136     if (qx) {
137         q = ( q ? (q + '&' + qx) : qx );
138     }    
139     if (options.type.toUpperCase() == 'GET') {
140         options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
141         options.data = null;  // data is null for 'get'
142     }
143     else {
144         options.data = q; // data is the query string for 'post'
145     }
147     var callbacks = [];
148     if (options.resetForm) {
149         callbacks.push(function() { $form.resetForm(); });
150     }
151     if (options.clearForm) {
152         callbacks.push(function() { $form.clearForm(options.includeHidden); });
153     }
155     // perform a load on the target only if dataType is not provided
156     if (!options.dataType && options.target) {
157         var oldSuccess = options.success || function(){};
158         callbacks.push(function(data) {
159             var fn = options.replaceTarget ? 'replaceWith' : 'html';
160             $(options.target)[fn](data).each(oldSuccess, arguments);
161         });
162     }
163     else if (options.success) {
164         callbacks.push(options.success);
165     }
167     options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
168         var context = options.context || this ;    // jQuery 1.4+ supports scope context 
169         for (var i=0, max=callbacks.length; i < max; i++) {
170             callbacks[i].apply(context, [data, status, xhr || $form, $form]);
171         }
172     };
174     // are there files to upload?
175     var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
176     var hasFileInputs = fileInputs.length > 0;
177     var mp = 'multipart/form-data';
178     var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
180     var fileAPI = feature.fileapi && feature.formdata;
181     log("fileAPI :" + fileAPI);
182     var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
184     // options.iframe allows user to force iframe mode
185     // 06-NOV-09: now defaulting to iframe mode if file input is detected
186     if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
187         // hack to fix Safari hang (thanks to Tim Molendijk for this)
188         // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
189         if (options.closeKeepAlive) {
190             $.get(options.closeKeepAlive, function() {
191                 fileUploadIframe(a);
192             });
193         }
194           else {
195             fileUploadIframe(a);
196           }
197     }
198     else if ((hasFileInputs || multipart) && fileAPI) {
199         fileUploadXhr(a);
200     }
201     else {
202         $.ajax(options);
203     }
205     // clear element array
206     for (var k=0; k < elements.length; k++)
207         elements[k] = null;
209     // fire 'notify' event
210     this.trigger('form-submit-notify', [this, options]);
211     return this;
213      // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
214     function fileUploadXhr(a) {
215         var formdata = new FormData();
217         for (var i=0; i < a.length; i++) {
218             formdata.append(a[i].name, a[i].value);
219         }
221         if (options.extraData) {
222             for (var p in options.extraData)
223                 if (options.extraData.hasOwnProperty(p))
224                     formdata.append(p, options.extraData[p]);
225         }
227         options.data = null;
229         var s = $.extend(true, {}, $.ajaxSettings, options, {
230             contentType: false,
231             processData: false,
232             cache: false,
233             type: 'POST'
234         });
235         
236         if (options.uploadProgress) {
237             // workaround because jqXHR does not expose upload property
238             s.xhr = function() {
239                 var xhr = jQuery.ajaxSettings.xhr();
240                 if (xhr.upload) {
241                     xhr.upload.onprogress = function(event) {
242                         var percent = 0;
243                         var position = event.loaded || event.position; /*event.position is deprecated*/
244                         var total = event.total;
245                         if (event.lengthComputable) {
246                             percent = Math.ceil(position / total * 100);
247                         }
248                         options.uploadProgress(event, position, total, percent);
249                     };
250                 }
251                 return xhr;
252             };
253         }
255         s.data = null;
256             var beforeSend = s.beforeSend;
257             s.beforeSend = function(xhr, o) {
258                 o.data = formdata;
259                 if(beforeSend)
260                     beforeSend.call(this, xhr, o);
261         };
262         $.ajax(s);
263     }
265     // private function for handling file uploads (hat tip to YAHOO!)
266     function fileUploadIframe(a) {
267         var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
268         var useProp = !!$.fn.prop;
270         if ($(':input[name=submit],:input[id=submit]', form).length) {
271             // if there is an input with a name or id of 'submit' then we won't be
272             // able to invoke the submit fn on the form (at least not x-browser)
273             alert('Error: Form elements must not have name or id of "submit".');
274             return;
275         }
276         
277         if (a) {
278             // ensure that every serialized input is still enabled
279             for (i=0; i < elements.length; i++) {
280                 el = $(elements[i]);
281                 if ( useProp )
282                     el.prop('disabled', false);
283                 else
284                     el.removeAttr('disabled');
285             }
286         }
288         s = $.extend(true, {}, $.ajaxSettings, options);
289         s.context = s.context || s;
290         id = 'jqFormIO' + (new Date().getTime());
291         if (s.iframeTarget) {
292             $io = $(s.iframeTarget);
293             n = $io.attr('name');
294             if (!n)
295                  $io.attr('name', id);
296             else
297                 id = n;
298         }
299         else {
300             $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
301             $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
302         }
303         io = $io[0];
306         xhr = { // mock object
307             aborted: 0,
308             responseText: null,
309             responseXML: null,
310             status: 0,
311             statusText: 'n/a',
312             getAllResponseHeaders: function() {},
313             getResponseHeader: function() {},
314             setRequestHeader: function() {},
315             abort: function(status) {
316                 var e = (status === 'timeout' ? 'timeout' : 'aborted');
317                 log('aborting upload... ' + e);
318                 this.aborted = 1;
319                 // #214
320                 if (io.contentWindow.document.execCommand) {
321                     try { // #214
322                         io.contentWindow.document.execCommand('Stop');
323                     } catch(ignore) {}
324                 }
325                 $io.attr('src', s.iframeSrc); // abort op in progress
326                 xhr.error = e;
327                 if (s.error)
328                     s.error.call(s.context, xhr, e, status);
329                 if (g)
330                     $.event.trigger("ajaxError", [xhr, s, e]);
331                 if (s.complete)
332                     s.complete.call(s.context, xhr, e);
333             }
334         };
336         g = s.global;
337         // trigger ajax global events so that activity/block indicators work like normal
338         if (g && 0 === $.active++) {
339             $.event.trigger("ajaxStart");
340         }
341         if (g) {
342             $.event.trigger("ajaxSend", [xhr, s]);
343         }
345         if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
346             if (s.global) {
347                 $.active--;
348             }
349             return;
350         }
351         if (xhr.aborted) {
352             return;
353         }
355         // add submitting element to data if we know it
356         sub = form.clk;
357         if (sub) {
358             n = sub.name;
359             if (n && !sub.disabled) {
360                 s.extraData = s.extraData || {};
361                 s.extraData[n] = sub.value;
362                 if (sub.type == "image") {
363                     s.extraData[n+'.x'] = form.clk_x;
364                     s.extraData[n+'.y'] = form.clk_y;
365                 }
366             }
367         }
368         
369         var CLIENT_TIMEOUT_ABORT = 1;
370         var SERVER_ABORT = 2;
372         function getDoc(frame) {
373             var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
374             return doc;
375         }
376         
377         // Rails CSRF hack (thanks to Yvan Barthelemy)
378         var csrf_token = $('meta[name=csrf-token]').attr('content');
379         var csrf_param = $('meta[name=csrf-param]').attr('content');
380         if (csrf_param && csrf_token) {
381             s.extraData = s.extraData || {};
382             s.extraData[csrf_param] = csrf_token;
383         }
385         // take a breath so that pending repaints get some cpu time before the upload starts
386         function doSubmit() {
387             // make sure form attrs are set
388             var t = $form.attr('target'), a = $form.attr('action');
390             // update form attrs in IE friendly way
391             form.setAttribute('target',id);
392             if (!method) {
393                 form.setAttribute('method', 'POST');
394             }
395             if (a != s.url) {
396                 form.setAttribute('action', s.url);
397             }
399             // ie borks in some cases when setting encoding
400             if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
401                 $form.attr({
402                     encoding: 'multipart/form-data',
403                     enctype:  'multipart/form-data'
404                 });
405             }
407             // support timout
408             if (s.timeout) {
409                 timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
410             }
411             
412             // look for server aborts
413             function checkState() {
414                 try {
415                     var state = getDoc(io).readyState;
416                     log('state = ' + state);
417                     if (state && state.toLowerCase() == 'uninitialized')
418                         setTimeout(checkState,50);
419                 }
420                 catch(e) {
421                     log('Server abort: ' , e, ' (', e.name, ')');
422                     cb(SERVER_ABORT);
423                     if (timeoutHandle)
424                         clearTimeout(timeoutHandle);
425                     timeoutHandle = undefined;
426                 }
427             }
429             // add "extra" data to form if provided in options
430             var extraInputs = [];
431             try {
432                 if (s.extraData) {
433                     for (var n in s.extraData) {
434                         if (s.extraData.hasOwnProperty(n)) {
435                            // if using the $.param format that allows for multiple values with the same name
436                            if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
437                                extraInputs.push(
438                                $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
439                                    .appendTo(form)[0]);
440                            } else {
441                                extraInputs.push(
442                                $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
443                                    .appendTo(form)[0]);
444                            }
445                         }
446                     }
447                 }
449                 if (!s.iframeTarget) {
450                     // add iframe to doc and submit the form
451                     $io.appendTo('body');
452                     if (io.attachEvent)
453                         io.attachEvent('onload', cb);
454                     else
455                         io.addEventListener('load', cb, false);
456                 }
457                 setTimeout(checkState,15);
458                 form.submit();
459             }
460             finally {
461                 // reset attrs and remove "extra" input elements
462                 form.setAttribute('action',a);
463                 if(t) {
464                     form.setAttribute('target', t);
465                 } else {
466                     $form.removeAttr('target');
467                 }
468                 $(extraInputs).remove();
469             }
470         }
472         if (s.forceSync) {
473             doSubmit();
474         }
475         else {
476             setTimeout(doSubmit, 10); // this lets dom updates render
477         }
479         var data, doc, domCheckCount = 50, callbackProcessed;
481         function cb(e) {
482             if (xhr.aborted || callbackProcessed) {
483                 return;
484             }
485             try {
486                 doc = getDoc(io);
487             }
488             catch(ex) {
489                 log('cannot access response document: ', ex);
490                 e = SERVER_ABORT;
491             }
492             if (e === CLIENT_TIMEOUT_ABORT && xhr) {
493                 xhr.abort('timeout');
494                 return;
495             }
496             else if (e == SERVER_ABORT && xhr) {
497                 xhr.abort('server abort');
498                 return;
499             }
501             if (!doc || doc.location.href == s.iframeSrc) {
502                 // response not received yet
503                 if (!timedOut)
504                     return;
505             }
506             if (io.detachEvent)
507                 io.detachEvent('onload', cb);
508             else    
509                 io.removeEventListener('load', cb, false);
511             var status = 'success', errMsg;
512             try {
513                 if (timedOut) {
514                     throw 'timeout';
515                 }
517                 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
518                 log('isXml='+isXml);
519                 if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
520                     if (--domCheckCount) {
521                         // in some browsers (Opera) the iframe DOM is not always traversable when
522                         // the onload callback fires, so we loop a bit to accommodate
523                         log('requeing onLoad callback, DOM not available');
524                         setTimeout(cb, 250);
525                         return;
526                     }
527                     // let this fall through because server response could be an empty document
528                     //log('Could not access iframe DOM after mutiple tries.');
529                     //throw 'DOMException: not available';
530                 }
532                 //log('response detected');
533                 var docRoot = doc.body ? doc.body : doc.documentElement;
534                 xhr.responseText = docRoot ? docRoot.innerHTML : null;
535                 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
536                 if (isXml)
537                     s.dataType = 'xml';
538                 xhr.getResponseHeader = function(header){
539                     var headers = {'content-type': s.dataType};
540                     return headers[header];
541                 };
542                 // support for XHR 'status' & 'statusText' emulation :
543                 if (docRoot) {
544                     xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
545                     xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
546                 }
548                 var dt = (s.dataType || '').toLowerCase();
549                 var scr = /(json|script|text)/.test(dt);
550                 if (scr || s.textarea) {
551                     // see if user embedded response in textarea
552                     var ta = doc.getElementsByTagName('textarea')[0];
553                     if (ta) {
554                         xhr.responseText = ta.value;
555                         // support for XHR 'status' & 'statusText' emulation :
556                         xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
557                         xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
558                     }
559                     else if (scr) {
560                         // account for browsers injecting pre around json response
561                         var pre = doc.getElementsByTagName('pre')[0];
562                         var b = doc.getElementsByTagName('body')[0];
563                         if (pre) {
564                             xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
565                         }
566                         else if (b) {
567                             xhr.responseText = b.textContent ? b.textContent : b.innerText;
568                         }
569                     }
570                 }
571                 else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
572                     xhr.responseXML = toXml(xhr.responseText);
573                 }
575                 try {
576                     data = httpData(xhr, dt, s);
577                 }
578                 catch (e) {
579                     status = 'parsererror';
580                     xhr.error = errMsg = (e || status);
581                 }
582             }
583             catch (e) {
584                 log('error caught: ',e);
585                 status = 'error';
586                 xhr.error = errMsg = (e || status);
587             }
589             if (xhr.aborted) {
590                 log('upload aborted');
591                 status = null;
592             }
594             if (xhr.status) { // we've set xhr.status
595                 status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
596             }
598             // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
599             if (status === 'success') {
600                 if (s.success)
601                     s.success.call(s.context, data, 'success', xhr);
602                 if (g)
603                     $.event.trigger("ajaxSuccess", [xhr, s]);
604             }
605             else if (status) {
606                 if (errMsg === undefined)
607                     errMsg = xhr.statusText;
608                 if (s.error)
609                     s.error.call(s.context, xhr, status, errMsg);
610                 if (g)
611                     $.event.trigger("ajaxError", [xhr, s, errMsg]);
612             }
614             if (g)
615                 $.event.trigger("ajaxComplete", [xhr, s]);
617             if (g && ! --$.active) {
618                 $.event.trigger("ajaxStop");
619             }
621             if (s.complete)
622                 s.complete.call(s.context, xhr, status);
624             callbackProcessed = true;
625             if (s.timeout)
626                 clearTimeout(timeoutHandle);
628             // clean up
629             setTimeout(function() {
630                 if (!s.iframeTarget)
631                     $io.remove();
632                 xhr.responseXML = null;
633             }, 100);
634         }
636         var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
637             if (window.ActiveXObject) {
638                 doc = new ActiveXObject('Microsoft.XMLDOM');
639                 doc.async = 'false';
640                 doc.loadXML(s);
641             }
642             else {
643                 doc = (new DOMParser()).parseFromString(s, 'text/xml');
644             }
645             return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
646         };
647         var parseJSON = $.parseJSON || function(s) {
648             /*jslint evil:true */
649             return window['eval']('(' + s + ')');
650         };
652         var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
654             var ct = xhr.getResponseHeader('content-type') || '',
655                 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
656                 data = xml ? xhr.responseXML : xhr.responseText;
658             if (xml && data.documentElement.nodeName === 'parsererror') {
659                 if ($.error)
660                     $.error('parsererror');
661             }
662             if (s && s.dataFilter) {
663                 data = s.dataFilter(data, type);
664             }
665             if (typeof data === 'string') {
666                 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
667                     data = parseJSON(data);
668                 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
669                     $.globalEval(data);
670                 }
671             }
672             return data;
673         };
674     }
678  * ajaxForm() provides a mechanism for fully automating form submission.
680  * The advantages of using this method instead of ajaxSubmit() are:
682  * 1: This method will include coordinates for <input type="image" /> elements (if the element
683  *    is used to submit the form).
684  * 2. This method will include the submit element's name/value data (for the element that was
685  *    used to submit the form).
686  * 3. This method binds the submit() method to the form for you.
688  * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
689  * passes the options argument along after properly binding events for submit elements and
690  * the form itself.
691  */
692 $.fn.ajaxForm = function(options) {
693     options = options || {};
694     options.delegation = options.delegation && $.isFunction($.fn.on);
695     
696     // in jQuery 1.3+ we can fix mistakes with the ready state
697     if (!options.delegation && this.length === 0) {
698         var o = { s: this.selector, c: this.context };
699         if (!$.isReady && o.s) {
700             log('DOM not ready, queuing ajaxForm');
701             $(function() {
702                 $(o.s,o.c).ajaxForm(options);
703             });
704             return this;
705         }
706         // is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
707         log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
708         return this;
709     }
711     if ( options.delegation ) {
712         $(document)
713             .off('submit.form-plugin', this.selector, doAjaxSubmit)
714             .off('click.form-plugin', this.selector, captureSubmittingElement)
715             .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
716             .on('click.form-plugin', this.selector, options, captureSubmittingElement);
717         return this;
718     }
720     return this.ajaxFormUnbind()
721         .bind('submit.form-plugin', options, doAjaxSubmit)
722         .bind('click.form-plugin', options, captureSubmittingElement);
725 // private event handlers    
726 function doAjaxSubmit(e) {
727     /*jshint validthis:true */
728     var options = e.data;
729     if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
730         e.preventDefault();
731         $(this).ajaxSubmit(options);
732     }
734     
735 function captureSubmittingElement(e) {
736     /*jshint validthis:true */
737     var target = e.target;
738     var $el = $(target);
739     if (!($el.is(":submit,input:image"))) {
740         // is this a child element of the submit el?  (ex: a span within a button)
741         var t = $el.closest(':submit');
742         if (t.length === 0) {
743             return;
744         }
745         target = t[0];
746     }
747     var form = this;
748     form.clk = target;
749     if (target.type == 'image') {
750         if (e.offsetX !== undefined) {
751             form.clk_x = e.offsetX;
752             form.clk_y = e.offsetY;
753         } else if (typeof $.fn.offset == 'function') {
754             var offset = $el.offset();
755             form.clk_x = e.pageX - offset.left;
756             form.clk_y = e.pageY - offset.top;
757         } else {
758             form.clk_x = e.pageX - target.offsetLeft;
759             form.clk_y = e.pageY - target.offsetTop;
760         }
761     }
762     // clear form vars
763     setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
767 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
768 $.fn.ajaxFormUnbind = function() {
769     return this.unbind('submit.form-plugin click.form-plugin');
773  * formToArray() gathers form element data into an array of objects that can
774  * be passed to any of the following ajax functions: $.get, $.post, or load.
775  * Each object in the array has both a 'name' and 'value' property.  An example of
776  * an array for a simple login form might be:
778  * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
780  * It is this array that is passed to pre-submit callback functions provided to the
781  * ajaxSubmit() and ajaxForm() methods.
782  */
783 $.fn.formToArray = function(semantic, elements) {
784     var a = [];
785     if (this.length === 0) {
786         return a;
787     }
789     var form = this[0];
790     var els = semantic ? form.getElementsByTagName('*') : form.elements;
791     if (!els) {
792         return a;
793     }
795     var i,j,n,v,el,max,jmax;
796     for(i=0, max=els.length; i < max; i++) {
797         el = els[i];
798         n = el.name;
799         if (!n) {
800             continue;
801         }
803         if (semantic && form.clk && el.type == "image") {
804             // handle image inputs on the fly when semantic == true
805             if(!el.disabled && form.clk == el) {
806                 a.push({name: n, value: $(el).val(), type: el.type });
807                 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
808             }
809             continue;
810         }
812         v = $.fieldValue(el, true);
813         if (v && v.constructor == Array) {
814             if (elements) 
815                 elements.push(el);
816             for(j=0, jmax=v.length; j < jmax; j++) {
817                 a.push({name: n, value: v[j]});
818             }
819         }
820         else if (feature.fileapi && el.type == 'file' && !el.disabled) {
821             if (elements) 
822                 elements.push(el);
823             var files = el.files;
824             if (files.length) {
825                 for (j=0; j < files.length; j++) {
826                     a.push({name: n, value: files[j], type: el.type});
827                 }
828             }
829             else {
830                 // #180
831                 a.push({ name: n, value: '', type: el.type });
832             }
833         }
834         else if (v !== null && typeof v != 'undefined') {
835             if (elements) 
836                 elements.push(el);
837             a.push({name: n, value: v, type: el.type, required: el.required});
838         }
839     }
841     if (!semantic && form.clk) {
842         // input type=='image' are not found in elements array! handle it here
843         var $input = $(form.clk), input = $input[0];
844         n = input.name;
845         if (n && !input.disabled && input.type == 'image') {
846             a.push({name: n, value: $input.val()});
847             a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
848         }
849     }
850     return a;
854  * Serializes form data into a 'submittable' string. This method will return a string
855  * in the format: name1=value1&amp;name2=value2
856  */
857 $.fn.formSerialize = function(semantic) {
858     //hand off to jQuery.param for proper encoding
859     return $.param(this.formToArray(semantic));
863  * Serializes all field elements in the jQuery object into a query string.
864  * This method will return a string in the format: name1=value1&amp;name2=value2
865  */
866 $.fn.fieldSerialize = function(successful) {
867     var a = [];
868     this.each(function() {
869         var n = this.name;
870         if (!n) {
871             return;
872         }
873         var v = $.fieldValue(this, successful);
874         if (v && v.constructor == Array) {
875             for (var i=0,max=v.length; i < max; i++) {
876                 a.push({name: n, value: v[i]});
877             }
878         }
879         else if (v !== null && typeof v != 'undefined') {
880             a.push({name: this.name, value: v});
881         }
882     });
883     //hand off to jQuery.param for proper encoding
884     return $.param(a);
888  * Returns the value(s) of the element in the matched set.  For example, consider the following form:
890  *  <form><fieldset>
891  *      <input name="A" type="text" />
892  *      <input name="A" type="text" />
893  *      <input name="B" type="checkbox" value="B1" />
894  *      <input name="B" type="checkbox" value="B2"/>
895  *      <input name="C" type="radio" value="C1" />
896  *      <input name="C" type="radio" value="C2" />
897  *  </fieldset></form>
899  *  var v = $(':text').fieldValue();
900  *  // if no values are entered into the text inputs
901  *  v == ['','']
902  *  // if values entered into the text inputs are 'foo' and 'bar'
903  *  v == ['foo','bar']
905  *  var v = $(':checkbox').fieldValue();
906  *  // if neither checkbox is checked
907  *  v === undefined
908  *  // if both checkboxes are checked
909  *  v == ['B1', 'B2']
911  *  var v = $(':radio').fieldValue();
912  *  // if neither radio is checked
913  *  v === undefined
914  *  // if first radio is checked
915  *  v == ['C1']
917  * The successful argument controls whether or not the field element must be 'successful'
918  * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
919  * The default value of the successful argument is true.  If this value is false the value(s)
920  * for each element is returned.
922  * Note: This method *always* returns an array.  If no valid value can be determined the
923  *    array will be empty, otherwise it will contain one or more values.
924  */
925 $.fn.fieldValue = function(successful) {
926     for (var val=[], i=0, max=this.length; i < max; i++) {
927         var el = this[i];
928         var v = $.fieldValue(el, successful);
929         if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
930             continue;
931         }
932         if (v.constructor == Array)
933             $.merge(val, v);
934         else
935             val.push(v);
936     }
937     return val;
941  * Returns the value of the field element.
942  */
943 $.fieldValue = function(el, successful) {
944     var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
945     if (successful === undefined) {
946         successful = true;
947     }
949     if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
950         (t == 'checkbox' || t == 'radio') && !el.checked ||
951         (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
952         tag == 'select' && el.selectedIndex == -1)) {
953             return null;
954     }
956     if (tag == 'select') {
957         var index = el.selectedIndex;
958         if (index < 0) {
959             return null;
960         }
961         var a = [], ops = el.options;
962         var one = (t == 'select-one');
963         var max = (one ? index+1 : ops.length);
964         for(var i=(one ? index : 0); i < max; i++) {
965             var op = ops[i];
966             if (op.selected) {
967                 var v = op.value;
968                 if (!v) { // extra pain for IE...
969                     v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
970                 }
971                 if (one) {
972                     return v;
973                 }
974                 a.push(v);
975             }
976         }
977         return a;
978     }
979     return $(el).val();
983  * Clears the form data.  Takes the following actions on the form's input fields:
984  *  - input text fields will have their 'value' property set to the empty string
985  *  - select elements will have their 'selectedIndex' property set to -1
986  *  - checkbox and radio inputs will have their 'checked' property set to false
987  *  - inputs of type submit, button, reset, and hidden will *not* be effected
988  *  - button elements will *not* be effected
989  */
990 $.fn.clearForm = function(includeHidden) {
991     return this.each(function() {
992         $('input,select,textarea', this).clearFields(includeHidden);
993     });
997  * Clears the selected form elements.
998  */
999 $.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
1000     var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
1001     return this.each(function() {
1002         var t = this.type, tag = this.tagName.toLowerCase();
1003         if (re.test(t) || tag == 'textarea') {
1004             this.value = '';
1005         }
1006         else if (t == 'checkbox' || t == 'radio') {
1007             this.checked = false;
1008         }
1009         else if (tag == 'select') {
1010             this.selectedIndex = -1;
1011         }
1012         else if (includeHidden) {
1013             // includeHidden can be the value true, or it can be a selector string
1014             // indicating a special test; for example:
1015             //  $('#myForm').clearForm('.special:hidden')
1016             // the above would clean hidden inputs that have the class of 'special'
1017             if ( (includeHidden === true && /hidden/.test(t)) ||
1018                  (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
1019                 this.value = '';
1020         }
1021     });
1025  * Resets the form data.  Causes all form elements to be reset to their original value.
1026  */
1027 $.fn.resetForm = function() {
1028     return this.each(function() {
1029         // guard against an input with the name of 'reset'
1030         // note that IE reports the reset function as an 'object'
1031         if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
1032             this.reset();
1033         }
1034     });
1038  * Enables or disables any matching elements.
1039  */
1040 $.fn.enable = function(b) {
1041     if (b === undefined) {
1042         b = true;
1043     }
1044     return this.each(function() {
1045         this.disabled = !b;
1046     });
1050  * Checks/unchecks any matching checkboxes or radio buttons and
1051  * selects/deselects and matching option elements.
1052  */
1053 $.fn.selected = function(select) {
1054     if (select === undefined) {
1055         select = true;
1056     }
1057     return this.each(function() {
1058         var t = this.type;
1059         if (t == 'checkbox' || t == 'radio') {
1060             this.checked = select;
1061         }
1062         else if (this.tagName.toLowerCase() == 'option') {
1063             var $sel = $(this).parent('select');
1064             if (select && $sel[0] && $sel[0].type == 'select-one') {
1065                 // deselect all other options
1066                 $sel.find('option').selected(false);
1067             }
1068             this.selected = select;
1069         }
1070     });
1073 // expose debug var
1074 $.fn.ajaxSubmit.debug = false;
1076 // helper fn for console logging
1077 function log() {
1078     if (!$.fn.ajaxSubmit.debug) 
1079         return;
1080     var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
1081     if (window.console && window.console.log) {
1082         window.console.log(msg);
1083     }
1084     else if (window.opera && window.opera.postError) {
1085         window.opera.postError(msg);
1086     }
1089 })(jQuery);