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