Subversion-Projekte lars-tiefland.zeldi.de

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
2 lars 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
 */
56
var feature = {};
57
feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
58
feature.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
758
function 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
 
767
function 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
1109
function 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);