1/*!
2 * jQuery Form Plugin
3 * version: 3.20 (20-NOV-2012)
4 * @requires jQuery v1.5 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";
15
16/*
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,
22
23    $(document).ready(function() {
24        $('#myForm').on('submit', function(e) {
25            e.preventDefault(); // <-- important
26            $(this).ajaxSubmit({
27                target: '#output'
28            });
29        });
30    });
31
32    Use ajaxForm when you want the plugin to manage all the event binding
33    for you.  For example,
34
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:
43
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.
51*/
52
53/**
54 * Feature detection
55 */
56var feature = {};
57feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
58feature.formdata = window.FormData !== undefined;
59
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 */
66
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;
74
75    if (typeof options == 'function') {
76        options = { success: options };
77    }
78
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    }
87
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);
94
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    }
103
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    }
109
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    }
121
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    }
127
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    }
134
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    }
146
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    }
154
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    }
166
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    };
173
174    // are there files to upload?
175
176    // [value] (issue #113), also see comment:
177    // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
178    var fileInputs = $('input[type=file]:enabled[value!=""]', this);
179
180    var hasFileInputs = fileInputs.length > 0;
181    var mp = 'multipart/form-data';
182    var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
183
184    var fileAPI = feature.fileapi && feature.formdata;
185    log("fileAPI :" + fileAPI);
186    var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
187
188    var jqxhr;
189
190    // options.iframe allows user to force iframe mode
191    // 06-NOV-09: now defaulting to iframe mode if file input is detected
192    if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
193        // hack to fix Safari hang (thanks to Tim Molendijk for this)
194        // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
195        if (options.closeKeepAlive) {
196            $.get(options.closeKeepAlive, function() {
197                jqxhr = fileUploadIframe(a);
198            });
199        }
200        else {
201            jqxhr = fileUploadIframe(a);
202        }
203    }
204    else if ((hasFileInputs || multipart) && fileAPI) {
205        jqxhr = fileUploadXhr(a);
206    }
207    else {
208        jqxhr = $.ajax(options);
209    }
210
211    $form.removeData('jqxhr').data('jqxhr', jqxhr);
212
213    // clear element array
214    for (var k=0; k < elements.length; k++)
215        elements[k] = null;
216
217    // fire 'notify' event
218    this.trigger('form-submit-notify', [this, options]);
219    return this;
220
221    // utility fn for deep serialization
222    function deepSerialize(extraData){
223        var serialized = $.param(extraData).split('&');
224        var len = serialized.length;
225        var result = {};
226        var i, part;
227        for (i=0; i < len; i++) {
228            part = serialized[i].split('=');
229            result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]);
230        }
231        return result;
232    }
233
234     // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
235    function fileUploadXhr(a) {
236        var formdata = new FormData();
237
238        for (var i=0; i < a.length; i++) {
239            formdata.append(a[i].name, a[i].value);
240        }
241
242        if (options.extraData) {
243            var serializedData = deepSerialize(options.extraData);
244            for (var p in serializedData)
245                if (serializedData.hasOwnProperty(p))
246                    formdata.append(p, serializedData[p]);
247        }
248
249        options.data = null;
250
251        var s = $.extend(true, {}, $.ajaxSettings, options, {
252            contentType: false,
253            processData: false,
254            cache: false,
255            type: method || 'POST'
256        });
257
258        if (options.uploadProgress) {
259            // workaround because jqXHR does not expose upload property
260            s.xhr = function() {
261                var xhr = jQuery.ajaxSettings.xhr();
262                if (xhr.upload) {
263                    xhr.upload.onprogress = function(event) {
264                        var percent = 0;
265                        var position = event.loaded || event.position; /*event.position is deprecated*/
266                        var total = event.total;
267                        if (event.lengthComputable) {
268                            percent = Math.ceil(position / total * 100);
269                        }
270                        options.uploadProgress(event, position, total, percent);
271                    };
272                }
273                return xhr;
274            };
275        }
276
277        s.data = null;
278            var beforeSend = s.beforeSend;
279            s.beforeSend = function(xhr, o) {
280                o.data = formdata;
281                if(beforeSend)
282                    beforeSend.call(this, xhr, o);
283        };
284        return $.ajax(s);
285    }
286
287    // private function for handling file uploads (hat tip to YAHOO!)
288    function fileUploadIframe(a) {
289        var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
290        var useProp = !!$.fn.prop;
291        var deferred = $.Deferred();
292
293        if ($('[name=submit],[id=submit]', form).length) {
294            // if there is an input with a name or id of 'submit' then we won't be
295            // able to invoke the submit fn on the form (at least not x-browser)
296            alert('Error: Form elements must not have name or id of "submit".');
297            deferred.reject();
298            return deferred;
299        }
300
301        if (a) {
302            // ensure that every serialized input is still enabled
303            for (i=0; i < elements.length; i++) {
304                el = $(elements[i]);
305                if ( useProp )
306                    el.prop('disabled', false);
307                else
308                    el.removeAttr('disabled');
309            }
310        }
311
312        s = $.extend(true, {}, $.ajaxSettings, options);
313        s.context = s.context || s;
314        id = 'jqFormIO' + (new Date().getTime());
315        if (s.iframeTarget) {
316            $io = $(s.iframeTarget);
317            n = $io.attr('name');
318            if (!n)
319                 $io.attr('name', id);
320            else
321                id = n;
322        }
323        else {
324            $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
325            $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
326        }
327        io = $io[0];
328
329
330        xhr = { // mock object
331            aborted: 0,
332            responseText: null,
333            responseXML: null,
334            status: 0,
335            statusText: 'n/a',
336            getAllResponseHeaders: function() {},
337            getResponseHeader: function() {},
338            setRequestHeader: function() {},
339            abort: function(status) {
340                var e = (status === 'timeout' ? 'timeout' : 'aborted');
341                log('aborting upload... ' + e);
342                this.aborted = 1;
343                // #214
344                if (io.contentWindow.document.execCommand) {
345                    try { // #214
346                        io.contentWindow.document.execCommand('Stop');
347                    } catch(ignore) {}
348                }
349                $io.attr('src', s.iframeSrc); // abort op in progress
350                xhr.error = e;
351                if (s.error)
352                    s.error.call(s.context, xhr, e, status);
353                if (g)
354                    $.event.trigger("ajaxError", [xhr, s, e]);
355                if (s.complete)
356                    s.complete.call(s.context, xhr, e);
357            }
358        };
359
360        g = s.global;
361        // trigger ajax global events so that activity/block indicators work like normal
362        if (g && 0 === $.active++) {
363            $.event.trigger("ajaxStart");
364        }
365        if (g) {
366            $.event.trigger("ajaxSend", [xhr, s]);
367        }
368
369        if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
370            if (s.global) {
371                $.active--;
372            }
373            deferred.reject();
374            return deferred;
375        }
376        if (xhr.aborted) {
377            deferred.reject();
378            return deferred;
379        }
380
381        // add submitting element to data if we know it
382        sub = form.clk;
383        if (sub) {
384            n = sub.name;
385            if (n && !sub.disabled) {
386                s.extraData = s.extraData || {};
387                s.extraData[n] = sub.value;
388                if (sub.type == "image") {
389                    s.extraData[n+'.x'] = form.clk_x;
390                    s.extraData[n+'.y'] = form.clk_y;
391                }
392            }
393        }
394
395        var CLIENT_TIMEOUT_ABORT = 1;
396        var SERVER_ABORT = 2;
397
398        function getDoc(frame) {
399            var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
400            return doc;
401        }
402
403        // Rails CSRF hack (thanks to Yvan Barthelemy)
404        var csrf_token = $('meta[name=csrf-token]').attr('content');
405        var csrf_param = $('meta[name=csrf-param]').attr('content');
406        if (csrf_param && csrf_token) {
407            s.extraData = s.extraData || {};
408            s.extraData[csrf_param] = csrf_token;
409        }
410
411        // take a breath so that pending repaints get some cpu time before the upload starts
412        function doSubmit() {
413            // make sure form attrs are set
414            var t = $form.attr('target'), a = $form.attr('action');
415
416            // update form attrs in IE friendly way
417            form.setAttribute('target',id);
418            if (!method) {
419                form.setAttribute('method', 'POST');
420            }
421            if (a != s.url) {
422                form.setAttribute('action', s.url);
423            }
424
425            // ie borks in some cases when setting encoding
426            if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
427                $form.attr({
428                    encoding: 'multipart/form-data',
429                    enctype:  'multipart/form-data'
430                });
431            }
432
433            // support timout
434            if (s.timeout) {
435                timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
436            }
437
438            // look for server aborts
439            function checkState() {
440                try {
441                    var state = getDoc(io).readyState;
442                    log('state = ' + state);
443                    if (state && state.toLowerCase() == 'uninitialized')
444                        setTimeout(checkState,50);
445                }
446                catch(e) {
447                    log('Server abort: ' , e, ' (', e.name, ')');
448                    cb(SERVER_ABORT);
449                    if (timeoutHandle)
450                        clearTimeout(timeoutHandle);
451                    timeoutHandle = undefined;
452                }
453            }
454
455            // add "extra" data to form if provided in options
456            var extraInputs = [];
457            try {
458                if (s.extraData) {
459                    for (var n in s.extraData) {
460                        if (s.extraData.hasOwnProperty(n)) {
461                           // if using the $.param format that allows for multiple values with the same name
462                           if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
463                               extraInputs.push(
464                               $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
465                                   .appendTo(form)[0]);
466                           } else {
467                               extraInputs.push(
468                               $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
469                                   .appendTo(form)[0]);
470                           }
471                        }
472                    }
473                }
474
475                if (!s.iframeTarget) {
476                    // add iframe to doc and submit the form
477                    $io.appendTo('body');
478                    if (io.attachEvent)
479                        io.attachEvent('onload', cb);
480                    else
481                        io.addEventListener('load', cb, false);
482                }
483                setTimeout(checkState,15);
484                form.submit();
485            }
486            finally {
487                // reset attrs and remove "extra" input elements
488                form.setAttribute('action',a);
489                if(t) {
490                    form.setAttribute('target', t);
491                } else {
492                    $form.removeAttr('target');
493                }
494                $(extraInputs).remove();
495            }
496        }
497
498        if (s.forceSync) {
499            doSubmit();
500        }
501        else {
502            setTimeout(doSubmit, 10); // this lets dom updates render
503        }
504
505        var data, doc, domCheckCount = 50, callbackProcessed;
506
507        function cb(e) {
508            if (xhr.aborted || callbackProcessed) {
509                return;
510            }
511            try {
512                doc = getDoc(io);
513            }
514            catch(ex) {
515                log('cannot access response document: ', ex);
516                e = SERVER_ABORT;
517            }
518            if (e === CLIENT_TIMEOUT_ABORT && xhr) {
519                xhr.abort('timeout');
520                deferred.reject(xhr, 'timeout');
521                return;
522            }
523            else if (e == SERVER_ABORT && xhr) {
524                xhr.abort('server abort');
525                deferred.reject(xhr, 'error', 'server abort');
526                return;
527            }
528
529            if (!doc || doc.location.href == s.iframeSrc) {
530                // response not received yet
531                if (!timedOut)
532                    return;
533            }
534            if (io.detachEvent)
535                io.detachEvent('onload', cb);
536            else
537                io.removeEventListener('load', cb, false);
538
539            var status = 'success', errMsg;
540            try {
541                if (timedOut) {
542                    throw 'timeout';
543                }
544
545                var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
546                log('isXml='+isXml);
547                if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
548                    if (--domCheckCount) {
549                        // in some browsers (Opera) the iframe DOM is not always traversable when
550                        // the onload callback fires, so we loop a bit to accommodate
551                        log('requeing onLoad callback, DOM not available');
552                        setTimeout(cb, 250);
553                        return;
554                    }
555                    // let this fall through because server response could be an empty document
556                    //log('Could not access iframe DOM after mutiple tries.');
557                    //throw 'DOMException: not available';
558                }
559
560                //log('response detected');
561                var docRoot = doc.body ? doc.body : doc.documentElement;
562                xhr.responseText = docRoot ? docRoot.innerHTML : null;
563                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
564                if (isXml)
565                    s.dataType = 'xml';
566                xhr.getResponseHeader = function(header){
567                    var headers = {'content-type': s.dataType};
568                    return headers[header];
569                };
570                // support for XHR 'status' & 'statusText' emulation :
571                if (docRoot) {
572                    xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
573                    xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
574                }
575
576                var dt = (s.dataType || '').toLowerCase();
577                var scr = /(json|script|text)/.test(dt);
578                if (scr || s.textarea) {
579                    // see if user embedded response in textarea
580                    var ta = doc.getElementsByTagName('textarea')[0];
581                    if (ta) {
582                        xhr.responseText = ta.value;
583                        // support for XHR 'status' & 'statusText' emulation :
584                        xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
585                        xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
586                    }
587                    else if (scr) {
588                        // account for browsers injecting pre around json response
589                        var pre = doc.getElementsByTagName('pre')[0];
590                        var b = doc.getElementsByTagName('body')[0];
591                        if (pre) {
592                            xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
593                        }
594                        else if (b) {
595                            xhr.responseText = b.textContent ? b.textContent : b.innerText;
596                        }
597                    }
598                }
599                else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
600                    xhr.responseXML = toXml(xhr.responseText);
601                }
602
603                try {
604                    data = httpData(xhr, dt, s);
605                }
606                catch (e) {
607                    status = 'parsererror';
608                    xhr.error = errMsg = (e || status);
609                }
610            }
611            catch (e) {
612                log('error caught: ',e);
613                status = 'error';
614                xhr.error = errMsg = (e || status);
615            }
616
617            if (xhr.aborted) {
618                log('upload aborted');
619                status = null;
620            }
621
622            if (xhr.status) { // we've set xhr.status
623                status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
624            }
625
626            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
627            if (status === 'success') {
628                if (s.success)
629                    s.success.call(s.context, data, 'success', xhr);
630                deferred.resolve(xhr.responseText, 'success', xhr);
631                if (g)
632                    $.event.trigger("ajaxSuccess", [xhr, s]);
633            }
634            else if (status) {
635                if (errMsg === undefined)
636                    errMsg = xhr.statusText;
637                if (s.error)
638                    s.error.call(s.context, xhr, status, errMsg);
639                deferred.reject(xhr, 'error', errMsg);
640                if (g)
641                    $.event.trigger("ajaxError", [xhr, s, errMsg]);
642            }
643
644            if (g)
645                $.event.trigger("ajaxComplete", [xhr, s]);
646
647            if (g && ! --$.active) {
648                $.event.trigger("ajaxStop");
649            }
650
651            if (s.complete)
652                s.complete.call(s.context, xhr, status);
653
654            callbackProcessed = true;
655            if (s.timeout)
656                clearTimeout(timeoutHandle);
657
658            // clean up
659            setTimeout(function() {
660                if (!s.iframeTarget)
661                    $io.remove();
662                xhr.responseXML = null;
663            }, 100);
664        }
665
666        var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
667            if (window.ActiveXObject) {
668                doc = new ActiveXObject('Microsoft.XMLDOM');
669                doc.async = 'false';
670                doc.loadXML(s);
671            }
672            else {
673                doc = (new DOMParser()).parseFromString(s, 'text/xml');
674            }
675            return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
676        };
677        var parseJSON = $.parseJSON || function(s) {
678            /*jslint evil:true */
679            return window['eval']('(' + s + ')');
680        };
681
682        var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
683
684            var ct = xhr.getResponseHeader('content-type') || '',
685                xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
686                data = xml ? xhr.responseXML : xhr.responseText;
687
688            if (xml && data.documentElement.nodeName === 'parsererror') {
689                if ($.error)
690                    $.error('parsererror');
691            }
692            if (s && s.dataFilter) {
693                data = s.dataFilter(data, type);
694            }
695            if (typeof data === 'string') {
696                if (type === 'json' || !type && ct.indexOf('json') >= 0) {
697                    data = parseJSON(data);
698                } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
699                    $.globalEval(data);
700                }
701            }
702            return data;
703        };
704
705        return deferred;
706    }
707};
708
709/**
710 * ajaxForm() provides a mechanism for fully automating form submission.
711 *
712 * The advantages of using this method instead of ajaxSubmit() are:
713 *
714 * 1: This method will include coordinates for <input type="image" /> elements (if the element
715 *    is used to submit the form).
716 * 2. This method will include the submit element's name/value data (for the element that was
717 *    used to submit the form).
718 * 3. This method binds the submit() method to the form for you.
719 *
720 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
721 * passes the options argument along after properly binding events for submit elements and
722 * the form itself.
723 */
724$.fn.ajaxForm = function(options) {
725    options = options || {};
726    options.delegation = options.delegation && $.isFunction($.fn.on);
727
728    // in jQuery 1.3+ we can fix mistakes with the ready state
729    if (!options.delegation && this.length === 0) {
730        var o = { s: this.selector, c: this.context };
731        if (!$.isReady && o.s) {
732            log('DOM not ready, queuing ajaxForm');
733            $(function() {
734                $(o.s,o.c).ajaxForm(options);
735            });
736            return this;
737        }
738        // is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
739        log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
740        return this;
741    }
742
743    if ( options.delegation ) {
744        $(document)
745            .off('submit.form-plugin', this.selector, doAjaxSubmit)
746            .off('click.form-plugin', this.selector, captureSubmittingElement)
747            .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
748            .on('click.form-plugin', this.selector, options, captureSubmittingElement);
749        return this;
750    }
751
752    return this.ajaxFormUnbind()
753        .bind('submit.form-plugin', options, doAjaxSubmit)
754        .bind('click.form-plugin', options, captureSubmittingElement);
755};
756
757// private event handlers
758function doAjaxSubmit(e) {
759    /*jshint validthis:true */
760    var options = e.data;
761    if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
762        e.preventDefault();
763        $(this).ajaxSubmit(options);
764    }
765}
766
767function captureSubmittingElement(e) {
768    /*jshint validthis:true */
769    var target = e.target;
770    var $el = $(target);
771    if (!($el.is("[type=submit],[type=image]"))) {
772        // is this a child element of the submit el?  (ex: a span within a button)
773        var t = $el.closest('[type=submit]');
774        if (t.length === 0) {
775            return;
776        }
777        target = t[0];
778    }
779    var form = this;
780    form.clk = target;
781    if (target.type == 'image') {
782        if (e.offsetX !== undefined) {
783            form.clk_x = e.offsetX;
784            form.clk_y = e.offsetY;
785        } else if (typeof $.fn.offset == 'function') {
786            var offset = $el.offset();
787            form.clk_x = e.pageX - offset.left;
788            form.clk_y = e.pageY - offset.top;
789        } else {
790            form.clk_x = e.pageX - target.offsetLeft;
791            form.clk_y = e.pageY - target.offsetTop;
792        }
793    }
794    // clear form vars
795    setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
796}
797
798
799// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
800$.fn.ajaxFormUnbind = function() {
801    return this.unbind('submit.form-plugin click.form-plugin');
802};
803
804/**
805 * formToArray() gathers form element data into an array of objects that can
806 * be passed to any of the following ajax functions: $.get, $.post, or load.
807 * Each object in the array has both a 'name' and 'value' property.  An example of
808 * an array for a simple login form might be:
809 *
810 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
811 *
812 * It is this array that is passed to pre-submit callback functions provided to the
813 * ajaxSubmit() and ajaxForm() methods.
814 */
815$.fn.formToArray = function(semantic, elements) {
816    var a = [];
817    if (this.length === 0) {
818        return a;
819    }
820
821    var form = this[0];
822    var els = semantic ? form.getElementsByTagName('*') : form.elements;
823    if (!els) {
824        return a;
825    }
826
827    var i,j,n,v,el,max,jmax;
828    for(i=0, max=els.length; i < max; i++) {
829        el = els[i];
830        n = el.name;
831        if (!n) {
832            continue;
833        }
834
835        if (semantic && form.clk && el.type == "image") {
836            // handle image inputs on the fly when semantic == true
837            if(!el.disabled && form.clk == el) {
838                a.push({name: n, value: $(el).val(), type: el.type });
839                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
840            }
841            continue;
842        }
843
844        v = $.fieldValue(el, true);
845        if (v && v.constructor == Array) {
846            if (elements)
847                elements.push(el);
848            for(j=0, jmax=v.length; j < jmax; j++) {
849                a.push({name: n, value: v[j]});
850            }
851        }
852        else if (feature.fileapi && el.type == 'file' && !el.disabled) {
853            if (elements)
854                elements.push(el);
855            var files = el.files;
856            if (files.length) {
857                for (j=0; j < files.length; j++) {
858                    a.push({name: n, value: files[j], type: el.type});
859                }
860            }
861            else {
862                // #180
863                a.push({ name: n, value: '', type: el.type });
864            }
865        }
866        else if (v !== null && typeof v != 'undefined') {
867            if (elements)
868                elements.push(el);
869            a.push({name: n, value: v, type: el.type, required: el.required});
870        }
871    }
872
873    if (!semantic && form.clk) {
874        // input type=='image' are not found in elements array! handle it here
875        var $input = $(form.clk), input = $input[0];
876        n = input.name;
877        if (n && !input.disabled && input.type == 'image') {
878            a.push({name: n, value: $input.val()});
879            a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
880        }
881    }
882    return a;
883};
884
885/**
886 * Serializes form data into a 'submittable' string. This method will return a string
887 * in the format: name1=value1&amp;name2=value2
888 */
889$.fn.formSerialize = function(semantic) {
890    //hand off to jQuery.param for proper encoding
891    return $.param(this.formToArray(semantic));
892};
893
894/**
895 * Serializes all field elements in the jQuery object into a query string.
896 * This method will return a string in the format: name1=value1&amp;name2=value2
897 */
898$.fn.fieldSerialize = function(successful) {
899    var a = [];
900    this.each(function() {
901        var n = this.name;
902        if (!n) {
903            return;
904        }
905        var v = $.fieldValue(this, successful);
906        if (v && v.constructor == Array) {
907            for (var i=0,max=v.length; i < max; i++) {
908                a.push({name: n, value: v[i]});
909            }
910        }
911        else if (v !== null && typeof v != 'undefined') {
912            a.push({name: this.name, value: v});
913        }
914    });
915    //hand off to jQuery.param for proper encoding
916    return $.param(a);
917};
918
919/**
920 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
921 *
922 *  <form><fieldset>
923 *      <input name="A" type="text" />
924 *      <input name="A" type="text" />
925 *      <input name="B" type="checkbox" value="B1" />
926 *      <input name="B" type="checkbox" value="B2"/>
927 *      <input name="C" type="radio" value="C1" />
928 *      <input name="C" type="radio" value="C2" />
929 *  </fieldset></form>
930 *
931 *  var v = $('input[type=text]').fieldValue();
932 *  // if no values are entered into the text inputs
933 *  v == ['','']
934 *  // if values entered into the text inputs are 'foo' and 'bar'
935 *  v == ['foo','bar']
936 *
937 *  var v = $('input[type=checkbox]').fieldValue();
938 *  // if neither checkbox is checked
939 *  v === undefined
940 *  // if both checkboxes are checked
941 *  v == ['B1', 'B2']
942 *
943 *  var v = $('input[type=radio]').fieldValue();
944 *  // if neither radio is checked
945 *  v === undefined
946 *  // if first radio is checked
947 *  v == ['C1']
948 *
949 * The successful argument controls whether or not the field element must be 'successful'
950 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
951 * The default value of the successful argument is true.  If this value is false the value(s)
952 * for each element is returned.
953 *
954 * Note: This method *always* returns an array.  If no valid value can be determined the
955 *    array will be empty, otherwise it will contain one or more values.
956 */
957$.fn.fieldValue = function(successful) {
958    for (var val=[], i=0, max=this.length; i < max; i++) {
959        var el = this[i];
960        var v = $.fieldValue(el, successful);
961        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
962            continue;
963        }
964        if (v.constructor == Array)
965            $.merge(val, v);
966        else
967            val.push(v);
968    }
969    return val;
970};
971
972/**
973 * Returns the value of the field element.
974 */
975$.fieldValue = function(el, successful) {
976    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
977    if (successful === undefined) {
978        successful = true;
979    }
980
981    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
982        (t == 'checkbox' || t == 'radio') && !el.checked ||
983        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
984        tag == 'select' && el.selectedIndex == -1)) {
985            return null;
986    }
987
988    if (tag == 'select') {
989        var index = el.selectedIndex;
990        if (index < 0) {
991            return null;
992        }
993        var a = [], ops = el.options;
994        var one = (t == 'select-one');
995        var max = (one ? index+1 : ops.length);
996        for(var i=(one ? index : 0); i < max; i++) {
997            var op = ops[i];
998            if (op.selected) {
999                var v = op.value;
1000                if (!v) { // extra pain for IE...
1001                    v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
1002                }
1003                if (one) {
1004                    return v;
1005                }
1006                a.push(v);
1007            }
1008        }
1009        return a;
1010    }
1011    return $(el).val();
1012};
1013
1014/**
1015 * Clears the form data.  Takes the following actions on the form's input fields:
1016 *  - input text fields will have their 'value' property set to the empty string
1017 *  - select elements will have their 'selectedIndex' property set to -1
1018 *  - checkbox and radio inputs will have their 'checked' property set to false
1019 *  - inputs of type submit, button, reset, and hidden will *not* be effected
1020 *  - button elements will *not* be effected
1021 */
1022$.fn.clearForm = function(includeHidden) {
1023    return this.each(function() {
1024        $('input,select,textarea', this).clearFields(includeHidden);
1025    });
1026};
1027
1028/**
1029 * Clears the selected form elements.
1030 */
1031$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
1032    var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
1033    return this.each(function() {
1034        var t = this.type, tag = this.tagName.toLowerCase();
1035        if (re.test(t) || tag == 'textarea') {
1036            this.value = '';
1037        }
1038        else if (t == 'checkbox' || t == 'radio') {
1039            this.checked = false;
1040        }
1041        else if (tag == 'select') {
1042            this.selectedIndex = -1;
1043        }
1044        else if (includeHidden) {
1045            // includeHidden can be the value true, or it can be a selector string
1046            // indicating a special test; for example:
1047            //  $('#myForm').clearForm('.special:hidden')
1048            // the above would clean hidden inputs that have the class of 'special'
1049            if ( (includeHidden === true && /hidden/.test(t)) ||
1050                 (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
1051                this.value = '';
1052        }
1053    });
1054};
1055
1056/**
1057 * Resets the form data.  Causes all form elements to be reset to their original value.
1058 */
1059$.fn.resetForm = function() {
1060    return this.each(function() {
1061        // guard against an input with the name of 'reset'
1062        // note that IE reports the reset function as an 'object'
1063        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
1064            this.reset();
1065        }
1066    });
1067};
1068
1069/**
1070 * Enables or disables any matching elements.
1071 */
1072$.fn.enable = function(b) {
1073    if (b === undefined) {
1074        b = true;
1075    }
1076    return this.each(function() {
1077        this.disabled = !b;
1078    });
1079};
1080
1081/**
1082 * Checks/unchecks any matching checkboxes or radio buttons and
1083 * selects/deselects and matching option elements.
1084 */
1085$.fn.selected = function(select) {
1086    if (select === undefined) {
1087        select = true;
1088    }
1089    return this.each(function() {
1090        var t = this.type;
1091        if (t == 'checkbox' || t == 'radio') {
1092            this.checked = select;
1093        }
1094        else if (this.tagName.toLowerCase() == 'option') {
1095            var $sel = $(this).parent('select');
1096            if (select && $sel[0] && $sel[0].type == 'select-one') {
1097                // deselect all other options
1098                $sel.find('option').selected(false);
1099            }
1100            this.selected = select;
1101        }
1102    });
1103};
1104
1105// expose debug var
1106$.fn.ajaxSubmit.debug = false;
1107
1108// helper fn for console logging
1109function log() {
1110    if (!$.fn.ajaxSubmit.debug)
1111        return;
1112    var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
1113    if (window.console && window.console.log) {
1114        window.console.log(msg);
1115    }
1116    else if (window.opera && window.opera.postError) {
1117        window.opera.postError(msg);
1118    }
1119}
1120
1121})(jQuery);
1122