Project

General

Profile

Download (21.5 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
 * Revision: $Id$
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
    var a = this.formToArray(options.semantic);
73
    if (options.data) {
74
        options.extraData = options.data;
75
        for (var n in options.data)
76
            a.push( { name: n, value: options.data[n] } );
77
    }
78

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

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

    
92
    var q = $.param(a);
93

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

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

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

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

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

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

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

    
143

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

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

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

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

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

    
178
        var cbInvoked = 0;
179
        var timedOut = 0;
180

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

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

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

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

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

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

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

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

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

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

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

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

    
356
};
357

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
601
})(jQuery);
(9-9/11)