Project

General

Profile

Download (20.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * jQuery Form Plugin
3
 * version: 2.12 (06/07/2008)
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
(function($) {
12

    
13
/*
14
    Usage Note:  
15
    -----------
16
    Do not use both ajaxSubmit and ajaxForm on the same form.  These
17
    functions are intended to be exclusive.  Use ajaxSubmit if you want
18
    to bind your own submit handler to the form.  For example,
19

    
20
    $(document).ready(function() {
21
        $('#myForm').bind('submit', function() {
22
            $(this).ajaxSubmit({
23
                target: '#output'
24
            });
25
            return false; // <-- important!
26
        });
27
    });
28

    
29
    Use ajaxForm when you want the plugin to manage all the event binding
30
    for you.  For example,
31

    
32
    $(document).ready(function() {
33
        $('#myForm').ajaxForm({
34
            target: '#output'
35
        });
36
    });
37
        
38
    When using ajaxForm, the ajaxSubmit function will be invoked for you
39
    at the appropriate time.  
40
*/
41

    
42
/**
43
 * ajaxSubmit() provides a mechanism for immediately submitting 
44
 * an HTML form using AJAX.
45
 */
46
$.fn.ajaxSubmit = function(options) {
47
    // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
48
    if (!this.length) {
49
        log('ajaxSubmit: skipping submit process - no element selected');
50
        return this;
51
    }
52

    
53
    if (typeof options == 'function')
54
        options = { success: options };
55

    
56
    options = $.extend({
57
        url:  this.attr('action') || window.location.toString(),
58
        type: this.attr('method') || 'GET'
59
    }, options || {});
60

    
61
    // hook for manipulating the form data before it is extracted;
62
    // convenient for use with rich editors like tinyMCE or FCKEditor
63
    var veto = {};
64
    this.trigger('form-pre-serialize', [this, options, veto]);
65
    if (veto.veto) {
66
        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
67
        return this;
68
   }
69

    
70
    var a = this.formToArray(options.semantic);
71
    if (options.data) {
72
        options.extraData = options.data;
73
        for (var n in options.data)
74
            a.push( { name: n, value: options.data[n] } );
75
    }
76

    
77
    // give pre-submit callback an opportunity to abort the submit
78
    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
79
        log('ajaxSubmit: submit aborted via beforeSubmit callback');
80
        return this;
81
    }    
82

    
83
    // fire vetoable 'validate' event
84
    this.trigger('form-submit-validate', [a, this, options, veto]);
85
    if (veto.veto) {
86
        log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
87
        return this;
88
    }    
89

    
90
    var q = $.param(a);
91

    
92
    if (options.type.toUpperCase() == 'GET') {
93
        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
94
        options.data = null;  // data is null for 'get'
95
    }
96
    else
97
        options.data = q; // data is the query string for 'post'
98

    
99
    var $form = this, callbacks = [];
100
    if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
101
    if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
102

    
103
    // perform a load on the target only if dataType is not provided
104
    if (!options.dataType && options.target) {
105
        var oldSuccess = options.success || function(){};
106
        callbacks.push(function(data) {
107
            $(options.target).html(data).each(oldSuccess, arguments);
108
        });
109
    }
110
    else if (options.success)
111
        callbacks.push(options.success);
112

    
113
    options.success = function(data, status) {
114
        for (var i=0, max=callbacks.length; i < max; i++)
115
            callbacks[i](data, status, $form);
116
    };
117

    
118
    // are there files to upload?
119
    var files = $('input:file', this).fieldValue();
120
    var found = false;
121
    for (var j=0; j < files.length; j++)
122
        if (files[j])
123
            found = true;
124

    
125
    // options.iframe allows user to force iframe mode
126
   if (options.iframe || found) { 
127
       // hack to fix Safari hang (thanks to Tim Molendijk for this)
128
       // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
129
       if ($.browser.safari && options.closeKeepAlive)
130
           $.get(options.closeKeepAlive, fileUpload);
131
       else
132
           fileUpload();
133
       }
134
   else
135
       $.ajax(options);
136

    
137
    // fire 'notify' event
138
    this.trigger('form-submit-notify', [this, options]);
139
    return this;
140

    
141

    
142
    // private function for handling file uploads (hat tip to YAHOO!)
143
    function fileUpload() {
144
        var form = $form[0];
145
        
146
        if ($(':input[@name=submit]', form).length) {
147
            alert(t('Error: Form elements must not be named "submit".'));
148
            return;
149
        }
150
        
151
        var opts = $.extend({}, $.ajaxSettings, options);
152

    
153
        var id = 'jqFormIO' + (new Date().getTime());
154
        var $io = $('<iframe id="' + id + '" name="' + id + '" />');
155
        var io = $io[0];
156

    
157
        if ($.browser.msie || $.browser.opera) 
158
            io.src = 'javascript:false;document.write("");';
159
        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
160

    
161
        var xhr = { // mock object
162
            responseText: null,
163
            responseXML: null,
164
            status: 0,
165
            statusText: 'n/a',
166
            getAllResponseHeaders: function() {},
167
            getResponseHeader: function() {},
168
            setRequestHeader: function() {}
169
        };
170

    
171
        var g = opts.global;
172
        // trigger ajax global events so that activity/block indicators work like normal
173
        if (g && !$.active++) $.event.trigger("ajaxStart");
174
        if (g) $.event.trigger("ajaxSend", [xhr, opts]);
175

    
176
        var cbInvoked = 0;
177
        var timedOut = 0;
178

    
179
        // add submitting element to data if we know it
180
        var sub = form.clk;
181
        if (sub) {
182
            var n = sub.name;
183
            if (n && !sub.disabled) {
184
                options.extraData = options.extraData || {};
185
                options.extraData[n] = sub.value;
186
                if (sub.type == "image") {
187
                    options.extraData[name+'.x'] = form.clk_x;
188
                    options.extraData[name+'.y'] = form.clk_y;
189
                }
190
            }
191
        }
192
        
193
        // take a breath so that pending repaints get some cpu time before the upload starts
194
        setTimeout(function() {
195
            // make sure form attrs are set
196
            var t = $form.attr('target'), a = $form.attr('action');
197
            $form.attr({
198
                target:   id,
199
                encoding: 'multipart/form-data',
200
                enctype:  'multipart/form-data',
201
                method:   'POST',
202
                action:   opts.url
203
            });
204

    
205
            // support timout
206
            if (opts.timeout)
207
                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
208

    
209
            // add "extra" data to form if provided in options
210
            var extraInputs = [];
211
            try {
212
                if (options.extraData)
213
                    for (var n in options.extraData)
214
                        extraInputs.push(
215
                            $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
216
                                .appendTo(form)[0]);
217
            
218
                // add iframe to doc and submit the form
219
                $io.appendTo('body');
220
                io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
221
                form.submit();
222
            }
223
            finally {
224
                // reset attrs and remove "extra" input elements
225
                $form.attr('action', a);
226
                t ? $form.attr('target', t) : $form.removeAttr('target');
227
                $(extraInputs).remove();
228
            }
229
        }, 10);
230

    
231
        function cb() {
232
            if (cbInvoked++) return;
233
            
234
            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
235

    
236
            var operaHack = 0;
237
            var ok = true;
238
            try {
239
                if (timedOut) throw 'timeout';
240
                // extract the server response from the iframe
241
                var data, doc;
242

    
243
                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
244
                
245
                if (doc.body == null && !operaHack && $.browser.opera) {
246
                    // In Opera 9.2.x the iframe DOM is not always traversable when
247
                    // the onload callback fires so we give Opera 100ms to right itself
248
                    operaHack = 1;
249
                    cbInvoked--;
250
                    setTimeout(cb, 100);
251
                    return;
252
                }
253
                
254
                xhr.responseText = doc.body ? doc.body.innerHTML : null;
255
                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
256
                xhr.getResponseHeader = function(header){
257
                    var headers = {'content-type': opts.dataType};
258
                    return headers[header];
259
                };
260

    
261
                if (opts.dataType == 'json' || opts.dataType == 'script') {
262
                    var ta = doc.getElementsByTagName('textarea')[0];
263
                    xhr.responseText = ta ? ta.value : xhr.responseText;
264
                }
265
                else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
266
                    xhr.responseXML = toXml(xhr.responseText);
267
                }
268
                data = $.httpData(xhr, opts.dataType);
269
            }
270
            catch(e){
271
                ok = false;
272
                $.handleError(opts, xhr, 'error', e);
273
            }
274

    
275
            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
276
            if (ok) {
277
                opts.success(data, 'success');
278
                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
279
            }
280
            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
281
            if (g && ! --$.active) $.event.trigger("ajaxStop");
282
            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
283

    
284
            // clean up
285
            setTimeout(function() {
286
                $io.remove();
287
                xhr.responseXML = null;
288
            }, 100);
289
        };
290

    
291
        function toXml(s, doc) {
292
            if (window.ActiveXObject) {
293
                doc = new ActiveXObject('Microsoft.XMLDOM');
294
                doc.async = 'false';
295
                doc.loadXML(s);
296
            }
297
            else
298
                doc = (new DOMParser()).parseFromString(s, 'text/xml');
299
            return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
300
        };
301
    };
302
};
303

    
304
/**
305
 * ajaxForm() provides a mechanism for fully automating form submission.
306
 *
307
 * The advantages of using this method instead of ajaxSubmit() are:
308
 *
309
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
310
 *    is used to submit the form).
311
 * 2. This method will include the submit element's name/value data (for the element that was
312
 *    used to submit the form).
313
 * 3. This method binds the submit() method to the form for you.
314
 *
315
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
316
 * passes the options argument along after properly binding events for submit elements and
317
 * the form itself.
318
 */ 
319
$.fn.ajaxForm = function(options) {
320
    return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
321
        $(this).ajaxSubmit(options);
322
        return false;
323
    }).each(function() {
324
        // store options in hash
325
        $(":submit,input:image", this).bind('click.form-plugin',function(e) {
326
            var $form = this.form;
327
            $form.clk = this;
328
            if (this.type == 'image') {
329
                if (e.offsetX != undefined) {
330
                    $form.clk_x = e.offsetX;
331
                    $form.clk_y = e.offsetY;
332
                } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
333
                    var offset = $(this).offset();
334
                    $form.clk_x = e.pageX - offset.left;
335
                    $form.clk_y = e.pageY - offset.top;
336
                } else {
337
                    $form.clk_x = e.pageX - this.offsetLeft;
338
                    $form.clk_y = e.pageY - this.offsetTop;
339
                }
340
            }
341
            // clear form vars
342
            setTimeout(function() { $form.clk = $form.clk_x = $form.clk_y = null; }, 10);
343
        });
344
    });
345
};
346

    
347
// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
348
$.fn.ajaxFormUnbind = function() {
349
    this.unbind('submit.form-plugin');
350
    return this.each(function() {
351
        $(":submit,input:image", this).unbind('click.form-plugin');
352
    });
353

    
354
};
355

    
356
/**
357
 * formToArray() gathers form element data into an array of objects that can
358
 * be passed to any of the following ajax functions: $.get, $.post, or load.
359
 * Each object in the array has both a 'name' and 'value' property.  An example of
360
 * an array for a simple login form might be:
361
 *
362
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
363
 *
364
 * It is this array that is passed to pre-submit callback functions provided to the
365
 * ajaxSubmit() and ajaxForm() methods.
366
 */
367
$.fn.formToArray = function(semantic) {
368
    var a = [];
369
    if (this.length == 0) return a;
370

    
371
    var form = this[0];
372
    var els = semantic ? form.getElementsByTagName('*') : form.elements;
373
    if (!els) return a;
374
    for(var i=0, max=els.length; i < max; i++) {
375
        var el = els[i];
376
        var n = el.name;
377
        if (!n) continue;
378

    
379
        if (semantic && form.clk && el.type == "image") {
380
            // handle image inputs on the fly when semantic == true
381
            if(!el.disabled && form.clk == el)
382
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
383
            continue;
384
        }
385

    
386
        var v = $.fieldValue(el, true);
387
        if (v && v.constructor == Array) {
388
            for(var j=0, jmax=v.length; j < jmax; j++)
389
                a.push({name: n, value: v[j]});
390
        }
391
        else if (v !== null && typeof v != 'undefined')
392
            a.push({name: n, value: v});
393
    }
394

    
395
    if (!semantic && form.clk) {
396
        // input type=='image' are not found in elements array! handle them here
397
        var inputs = form.getElementsByTagName("input");
398
        for(var i=0, max=inputs.length; i < max; i++) {
399
            var input = inputs[i];
400
            var n = input.name;
401
            if(n && !input.disabled && input.type == "image" && form.clk == input)
402
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
403
        }
404
    }
405
    return a;
406
};
407

    
408
/**
409
 * Serializes form data into a 'submittable' string. This method will return a string
410
 * in the format: name1=value1&amp;name2=value2
411
 */
412
$.fn.formSerialize = function(semantic) {
413
    //hand off to jQuery.param for proper encoding
414
    return $.param(this.formToArray(semantic));
415
};
416

    
417
/**
418
 * Serializes all field elements in the jQuery object into a query string.
419
 * This method will return a string in the format: name1=value1&amp;name2=value2
420
 */
421
$.fn.fieldSerialize = function(successful) {
422
    var a = [];
423
    this.each(function() {
424
        var n = this.name;
425
        if (!n) return;
426
        var v = $.fieldValue(this, successful);
427
        if (v && v.constructor == Array) {
428
            for (var i=0,max=v.length; i < max; i++)
429
                a.push({name: n, value: v[i]});
430
        }
431
        else if (v !== null && typeof v != 'undefined')
432
            a.push({name: this.name, value: v});
433
    });
434
    //hand off to jQuery.param for proper encoding
435
    return $.param(a);
436
};
437

    
438
/**
439
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
440
 *
441
 *  <form><fieldset>
442
 *      <input name="A" type="text" />
443
 *      <input name="A" type="text" />
444
 *      <input name="B" type="checkbox" value="B1" />
445
 *      <input name="B" type="checkbox" value="B2"/>
446
 *      <input name="C" type="radio" value="C1" />
447
 *      <input name="C" type="radio" value="C2" />
448
 *  </fieldset></form>
449
 *
450
 *  var v = $(':text').fieldValue();
451
 *  // if no values are entered into the text inputs
452
 *  v == ['','']
453
 *  // if values entered into the text inputs are 'foo' and 'bar'
454
 *  v == ['foo','bar']
455
 *
456
 *  var v = $(':checkbox').fieldValue();
457
 *  // if neither checkbox is checked
458
 *  v === undefined
459
 *  // if both checkboxes are checked
460
 *  v == ['B1', 'B2']
461
 *
462
 *  var v = $(':radio').fieldValue();
463
 *  // if neither radio is checked
464
 *  v === undefined
465
 *  // if first radio is checked
466
 *  v == ['C1']
467
 *
468
 * The successful argument controls whether or not the field element must be 'successful'
469
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
470
 * The default value of the successful argument is true.  If this value is false the value(s)
471
 * for each element is returned.
472
 *
473
 * Note: This method *always* returns an array.  If no valid value can be determined the
474
 *       array will be empty, otherwise it will contain one or more values.
475
 */
476
$.fn.fieldValue = function(successful) {
477
    for (var val=[], i=0, max=this.length; i < max; i++) {
478
        var el = this[i];
479
        var v = $.fieldValue(el, successful);
480
        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
481
            continue;
482
        v.constructor == Array ? $.merge(val, v) : val.push(v);
483
    }
484
    return val;
485
};
486

    
487
/**
488
 * Returns the value of the field element.
489
 */
490
$.fieldValue = function(el, successful) {
491
    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
492
    if (typeof successful == 'undefined') successful = true;
493

    
494
    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
495
        (t == 'checkbox' || t == 'radio') && !el.checked ||
496
        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
497
        tag == 'select' && el.selectedIndex == -1))
498
            return null;
499

    
500
    if (tag == 'select') {
501
        var index = el.selectedIndex;
502
        if (index < 0) return null;
503
        var a = [], ops = el.options;
504
        var one = (t == 'select-one');
505
        var max = (one ? index+1 : ops.length);
506
        for(var i=(one ? index : 0); i < max; i++) {
507
            var op = ops[i];
508
            if (op.selected) {
509
                // extra pain for IE...
510
                var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
511
                if (one) return v;
512
                a.push(v);
513
            }
514
        }
515
        return a;
516
    }
517
    return el.value;
518
};
519

    
520
/**
521
 * Clears the form data.  Takes the following actions on the form's input fields:
522
 *  - input text fields will have their 'value' property set to the empty string
523
 *  - select elements will have their 'selectedIndex' property set to -1
524
 *  - checkbox and radio inputs will have their 'checked' property set to false
525
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
526
 *  - button elements will *not* be effected
527
 */
528
$.fn.clearForm = function() {
529
    return this.each(function() {
530
        $('input,select,textarea', this).clearFields();
531
    });
532
};
533

    
534
/**
535
 * Clears the selected form elements.
536
 */
537
$.fn.clearFields = $.fn.clearInputs = function() {
538
    return this.each(function() {
539
        var t = this.type, tag = this.tagName.toLowerCase();
540
        if (t == 'text' || t == 'password' || tag == 'textarea')
541
            this.value = '';
542
        else if (t == 'checkbox' || t == 'radio')
543
            this.checked = false;
544
        else if (tag == 'select')
545
            this.selectedIndex = -1;
546
    });
547
};
548

    
549
/**
550
 * Resets the form data.  Causes all form elements to be reset to their original value.
551
 */
552
$.fn.resetForm = function() {
553
    return this.each(function() {
554
        // guard against an input with the name of 'reset'
555
        // note that IE reports the reset function as an 'object'
556
        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
557
            this.reset();
558
    });
559
};
560

    
561
/**
562
 * Enables or disables any matching elements.
563
 */
564
$.fn.enable = function(b) { 
565
    if (b == undefined) b = true;
566
    return this.each(function() { 
567
        this.disabled = !b 
568
    });
569
};
570

    
571
/**
572
 * Checks/unchecks any matching checkboxes or radio buttons and
573
 * selects/deselects and matching option elements.
574
 */
575
$.fn.select = function(select) {
576
    if (select == undefined) select = true;
577
    return this.each(function() { 
578
        var t = this.type;
579
        if (t == 'checkbox' || t == 'radio')
580
            this.checked = select;
581
        else if (this.tagName.toLowerCase() == 'option') {
582
            var $sel = $(this).parent('select');
583
            if (select && $sel[0] && $sel[0].type == 'select-one') {
584
                // deselect all other options
585
                $sel.find('option').select(false);
586
            }
587
            this.selected = select;
588
        }
589
    });
590
};
591

    
592
// helper fn for console logging
593
// set $.fn.ajaxSubmit.debug to true to enable debug logging
594
function log() {
595
    if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
596
        window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
597
};
598

    
599
})(jQuery);
(11-11/15)