Хранилища Subversion ant

Редакция

Содержимое файла | Последнее изменение | Открыть журнал | RSS

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