Revert "merged master"
[lhc/web/wiklou.git] / resources / jquery / jquery.form.js
1 /*!
2 * jQuery Form Plugin
3 * version: 2.84 (12-AUG-2011)
4 * @requires jQuery v1.3.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(e) {
22 e.preventDefault(); // <-- important
23 $(this).ajaxSubmit({
24 target: '#output'
25 });
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 var method, action, url, $form = this;
54
55 if (typeof options == 'function') {
56 options = { success: options };
57 }
58
59 method = this.attr('method');
60 action = this.attr('action');
61 url = (typeof action === 'string') ? $.trim(action) : '';
62 url = url || window.location.href || '';
63 if (url) {
64 // clean url (don't include hash vaue)
65 url = (url.match(/^([^#]+)/)||[])[1];
66 }
67
68 options = $.extend(true, {
69 url: url,
70 success: $.ajaxSettings.success,
71 type: method || 'GET',
72 iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
73 }, options);
74
75 // hook for manipulating the form data before it is extracted;
76 // convenient for use with rich editors like tinyMCE or FCKEditor
77 var veto = {};
78 this.trigger('form-pre-serialize', [this, options, veto]);
79 if (veto.veto) {
80 log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
81 return this;
82 }
83
84 // provide opportunity to alter form data before it is serialized
85 if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
86 log('ajaxSubmit: submit aborted via beforeSerialize callback');
87 return this;
88 }
89
90 var n,v,a = this.formToArray(options.semantic);
91 if (options.data) {
92 options.extraData = options.data;
93 for (n in options.data) {
94 if( $.isArray(options.data[n]) ) {
95 for (var k in options.data[n]) {
96 a.push( { name: n, value: options.data[n][k] } );
97 }
98 }
99 else {
100 v = options.data[n];
101 v = $.isFunction(v) ? v() : v; // if value is fn, invoke it
102 a.push( { name: n, value: v } );
103 }
104 }
105 }
106
107 // give pre-submit callback an opportunity to abort the submit
108 if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
109 log('ajaxSubmit: submit aborted via beforeSubmit callback');
110 return this;
111 }
112
113 // fire vetoable 'validate' event
114 this.trigger('form-submit-validate', [a, this, options, veto]);
115 if (veto.veto) {
116 log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
117 return this;
118 }
119
120 var q = $.param(a);
121
122 if (options.type.toUpperCase() == 'GET') {
123 options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
124 options.data = null; // data is null for 'get'
125 }
126 else {
127 options.data = q; // data is the query string for 'post'
128 }
129
130 var callbacks = [];
131 if (options.resetForm) {
132 callbacks.push(function() { $form.resetForm(); });
133 }
134 if (options.clearForm) {
135 callbacks.push(function() { $form.clearForm(); });
136 }
137
138 // perform a load on the target only if dataType is not provided
139 if (!options.dataType && options.target) {
140 var oldSuccess = options.success || function(){};
141 callbacks.push(function(data) {
142 var fn = options.replaceTarget ? 'replaceWith' : 'html';
143 $(options.target)[fn](data).each(oldSuccess, arguments);
144 });
145 }
146 else if (options.success) {
147 callbacks.push(options.success);
148 }
149
150 options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
151 var context = options.context || options; // jQuery 1.4+ supports scope context
152 for (var i=0, max=callbacks.length; i < max; i++) {
153 callbacks[i].apply(context, [data, status, xhr || $form, $form]);
154 }
155 };
156
157 // are there files to upload?
158 var fileInputs = $('input:file', this).length > 0;
159 var mp = 'multipart/form-data';
160 var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
161
162 // options.iframe allows user to force iframe mode
163 // 06-NOV-09: now defaulting to iframe mode if file input is detected
164 if (options.iframe !== false && (fileInputs || options.iframe || multipart)) {
165 // hack to fix Safari hang (thanks to Tim Molendijk for this)
166 // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
167 if (options.closeKeepAlive) {
168 $.get(options.closeKeepAlive, function() { fileUpload(a); });
169 }
170 else {
171 fileUpload(a);
172 }
173 }
174 else {
175 // IE7 massage (see issue 57)
176 if ($.browser.msie && method == 'get') {
177 var ieMeth = $form[0].getAttribute('method');
178 if (typeof ieMeth === 'string')
179 options.type = ieMeth;
180 }
181 $.ajax(options);
182 }
183
184 // fire 'notify' event
185 this.trigger('form-submit-notify', [this, options]);
186 return this;
187
188
189 // private function for handling file uploads (hat tip to YAHOO!)
190 function fileUpload(a) {
191 var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
192 var useProp = !!$.fn.prop;
193
194 if (a) {
195 // ensure that every serialized input is still enabled
196 for (i=0; i < a.length; i++) {
197 el = $(form[a[i].name]);
198 el[ useProp ? 'prop' : 'attr' ]('disabled', false);
199 }
200 }
201
202 if ($(':input[name=submit],:input[id=submit]', form).length) {
203 // if there is an input with a name or id of 'submit' then we won't be
204 // able to invoke the submit fn on the form (at least not x-browser)
205 alert('Error: Form elements must not have name or id of "submit".');
206 return;
207 }
208
209 s = $.extend(true, {}, $.ajaxSettings, options);
210 s.context = s.context || s;
211 id = 'jqFormIO' + (new Date().getTime());
212 if (s.iframeTarget) {
213 $io = $(s.iframeTarget);
214 n = $io.attr('name');
215 if (n == null)
216 $io.attr('name', id);
217 else
218 id = n;
219 }
220 else {
221 $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
222 $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
223 }
224 io = $io[0];
225
226
227 xhr = { // mock object
228 aborted: 0,
229 responseText: null,
230 responseXML: null,
231 status: 0,
232 statusText: 'n/a',
233 getAllResponseHeaders: function() {},
234 getResponseHeader: function() {},
235 setRequestHeader: function() {},
236 abort: function(status) {
237 var e = (status === 'timeout' ? 'timeout' : 'aborted');
238 log('aborting upload... ' + e);
239 this.aborted = 1;
240 $io.attr('src', s.iframeSrc); // abort op in progress
241 xhr.error = e;
242 s.error && s.error.call(s.context, xhr, e, status);
243 g && $.event.trigger("ajaxError", [xhr, s, e]);
244 s.complete && s.complete.call(s.context, xhr, e);
245 }
246 };
247
248 g = s.global;
249 // trigger ajax global events so that activity/block indicators work like normal
250 if (g && ! $.active++) {
251 $.event.trigger("ajaxStart");
252 }
253 if (g) {
254 $.event.trigger("ajaxSend", [xhr, s]);
255 }
256
257 if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
258 if (s.global) {
259 $.active--;
260 }
261 return;
262 }
263 if (xhr.aborted) {
264 return;
265 }
266
267 // add submitting element to data if we know it
268 sub = form.clk;
269 if (sub) {
270 n = sub.name;
271 if (n && !sub.disabled) {
272 s.extraData = s.extraData || {};
273 s.extraData[n] = sub.value;
274 if (sub.type == "image") {
275 s.extraData[n+'.x'] = form.clk_x;
276 s.extraData[n+'.y'] = form.clk_y;
277 }
278 }
279 }
280
281 var CLIENT_TIMEOUT_ABORT = 1;
282 var SERVER_ABORT = 2;
283
284 function getDoc(frame) {
285 var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
286 return doc;
287 }
288
289 // take a breath so that pending repaints get some cpu time before the upload starts
290 function doSubmit() {
291 // make sure form attrs are set
292 var t = $form.attr('target'), a = $form.attr('action');
293
294 // update form attrs in IE friendly way
295 form.setAttribute('target',id);
296 if (!method) {
297 form.setAttribute('method', 'POST');
298 }
299 if (a != s.url) {
300 form.setAttribute('action', s.url);
301 }
302
303 // ie borks in some cases when setting encoding
304 if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
305 $form.attr({
306 encoding: 'multipart/form-data',
307 enctype: 'multipart/form-data'
308 });
309 }
310
311 // support timout
312 if (s.timeout) {
313 timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
314 }
315
316 // look for server aborts
317 function checkState() {
318 try {
319 var state = getDoc(io).readyState;
320 log('state = ' + state);
321 if (state.toLowerCase() == 'uninitialized')
322 setTimeout(checkState,50);
323 }
324 catch(e) {
325 log('Server abort: ' , e, ' (', e.name, ')');
326 cb(SERVER_ABORT);
327 timeoutHandle && clearTimeout(timeoutHandle);
328 timeoutHandle = undefined;
329 }
330 }
331
332 // add "extra" data to form if provided in options
333 var extraInputs = [];
334 try {
335 if (s.extraData) {
336 for (var n in s.extraData) {
337 extraInputs.push(
338 $('<input type="hidden" name="'+n+'" />').attr('value',s.extraData[n])
339 .appendTo(form)[0]);
340 }
341 }
342
343 if (!s.iframeTarget) {
344 // add iframe to doc and submit the form
345 $io.appendTo('body');
346 io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
347 }
348 setTimeout(checkState,15);
349 form.submit();
350 }
351 finally {
352 // reset attrs and remove "extra" input elements
353 form.setAttribute('action',a);
354 if(t) {
355 form.setAttribute('target', t);
356 } else {
357 $form.removeAttr('target');
358 }
359 $(extraInputs).remove();
360 }
361 }
362
363 if (s.forceSync) {
364 doSubmit();
365 }
366 else {
367 setTimeout(doSubmit, 10); // this lets dom updates render
368 }
369
370 var data, doc, domCheckCount = 50, callbackProcessed;
371
372 function cb(e) {
373 if (xhr.aborted || callbackProcessed) {
374 return;
375 }
376 try {
377 doc = getDoc(io);
378 }
379 catch(ex) {
380 log('cannot access response document: ', ex);
381 e = SERVER_ABORT;
382 }
383 if (e === CLIENT_TIMEOUT_ABORT && xhr) {
384 xhr.abort('timeout');
385 return;
386 }
387 else if (e == SERVER_ABORT && xhr) {
388 xhr.abort('server abort');
389 return;
390 }
391
392 if (!doc || doc.location.href == s.iframeSrc) {
393 // response not received yet
394 if (!timedOut)
395 return;
396 }
397 io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
398
399 var status = 'success', errMsg;
400 try {
401 if (timedOut) {
402 throw 'timeout';
403 }
404
405 var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
406 log('isXml='+isXml);
407 if (!isXml && window.opera && (doc.body == null || doc.body.innerHTML == '')) {
408 if (--domCheckCount) {
409 // in some browsers (Opera) the iframe DOM is not always traversable when
410 // the onload callback fires, so we loop a bit to accommodate
411 log('requeing onLoad callback, DOM not available');
412 setTimeout(cb, 250);
413 return;
414 }
415 // let this fall through because server response could be an empty document
416 //log('Could not access iframe DOM after mutiple tries.');
417 //throw 'DOMException: not available';
418 }
419
420 //log('response detected');
421 var docRoot = doc.body ? doc.body : doc.documentElement;
422 xhr.responseText = docRoot ? docRoot.innerHTML : null;
423 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
424 if (isXml)
425 s.dataType = 'xml';
426 xhr.getResponseHeader = function(header){
427 var headers = {'content-type': s.dataType};
428 return headers[header];
429 };
430 // support for XHR 'status' & 'statusText' emulation :
431 if (docRoot) {
432 xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
433 xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
434 }
435
436 var dt = s.dataType || '';
437 var scr = /(json|script|text)/.test(dt.toLowerCase());
438 if (scr || s.textarea) {
439 // see if user embedded response in textarea
440 var ta = doc.getElementsByTagName('textarea')[0];
441 if (ta) {
442 xhr.responseText = ta.value;
443 // support for XHR 'status' & 'statusText' emulation :
444 xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
445 xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
446 }
447 else if (scr) {
448 // account for browsers injecting pre around json response
449 var pre = doc.getElementsByTagName('pre')[0];
450 var b = doc.getElementsByTagName('body')[0];
451 if (pre) {
452 xhr.responseText = pre.textContent ? pre.textContent : pre.innerHTML;
453 }
454 else if (b) {
455 xhr.responseText = b.innerHTML;
456 }
457 }
458 }
459 else if (s.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
460 xhr.responseXML = toXml(xhr.responseText);
461 }
462
463 try {
464 data = httpData(xhr, s.dataType, s);
465 }
466 catch (e) {
467 status = 'parsererror';
468 xhr.error = errMsg = (e || status);
469 }
470 }
471 catch (e) {
472 log('error caught: ',e);
473 status = 'error';
474 xhr.error = errMsg = (e || status);
475 }
476
477 if (xhr.aborted) {
478 log('upload aborted');
479 status = null;
480 }
481
482 if (xhr.status) { // we've set xhr.status
483 status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
484 }
485
486 // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
487 if (status === 'success') {
488 s.success && s.success.call(s.context, data, 'success', xhr);
489 g && $.event.trigger("ajaxSuccess", [xhr, s]);
490 }
491 else if (status) {
492 if (errMsg == undefined)
493 errMsg = xhr.statusText;
494 s.error && s.error.call(s.context, xhr, status, errMsg);
495 g && $.event.trigger("ajaxError", [xhr, s, errMsg]);
496 }
497
498 g && $.event.trigger("ajaxComplete", [xhr, s]);
499
500 if (g && ! --$.active) {
501 $.event.trigger("ajaxStop");
502 }
503
504 s.complete && s.complete.call(s.context, xhr, status);
505
506 callbackProcessed = true;
507 if (s.timeout)
508 clearTimeout(timeoutHandle);
509
510 // clean up
511 setTimeout(function() {
512 if (!s.iframeTarget)
513 $io.remove();
514 xhr.responseXML = null;
515 }, 100);
516 }
517
518 var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
519 if (window.ActiveXObject) {
520 doc = new ActiveXObject('Microsoft.XMLDOM');
521 doc.async = 'false';
522 doc.loadXML(s);
523 }
524 else {
525 doc = (new DOMParser()).parseFromString(s, 'text/xml');
526 }
527 return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
528 };
529 var parseJSON = $.parseJSON || function(s) {
530 return window['eval']('(' + s + ')');
531 };
532
533 var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
534
535 var ct = xhr.getResponseHeader('content-type') || '',
536 xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
537 data = xml ? xhr.responseXML : xhr.responseText;
538
539 if (xml && data.documentElement.nodeName === 'parsererror') {
540 $.error && $.error('parsererror');
541 }
542 if (s && s.dataFilter) {
543 data = s.dataFilter(data, type);
544 }
545 if (typeof data === 'string') {
546 if (type === 'json' || !type && ct.indexOf('json') >= 0) {
547 data = parseJSON(data);
548 } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
549 $.globalEval(data);
550 }
551 }
552 return data;
553 };
554 }
555 };
556
557 /**
558 * ajaxForm() provides a mechanism for fully automating form submission.
559 *
560 * The advantages of using this method instead of ajaxSubmit() are:
561 *
562 * 1: This method will include coordinates for <input type="image" /> elements (if the element
563 * is used to submit the form).
564 * 2. This method will include the submit element's name/value data (for the element that was
565 * used to submit the form).
566 * 3. This method binds the submit() method to the form for you.
567 *
568 * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
569 * passes the options argument along after properly binding events for submit elements and
570 * the form itself.
571 */
572 $.fn.ajaxForm = function(options) {
573 // in jQuery 1.3+ we can fix mistakes with the ready state
574 if (this.length === 0) {
575 var o = { s: this.selector, c: this.context };
576 if (!$.isReady && o.s) {
577 log('DOM not ready, queuing ajaxForm');
578 $(function() {
579 $(o.s,o.c).ajaxForm(options);
580 });
581 return this;
582 }
583 // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
584 log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
585 return this;
586 }
587
588 return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
589 if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
590 e.preventDefault();
591 $(this).ajaxSubmit(options);
592 }
593 }).bind('click.form-plugin', function(e) {
594 var target = e.target;
595 var $el = $(target);
596 if (!($el.is(":submit,input:image"))) {
597 // is this a child element of the submit el? (ex: a span within a button)
598 var t = $el.closest(':submit');
599 if (t.length == 0) {
600 return;
601 }
602 target = t[0];
603 }
604 var form = this;
605 form.clk = target;
606 if (target.type == 'image') {
607 if (e.offsetX != undefined) {
608 form.clk_x = e.offsetX;
609 form.clk_y = e.offsetY;
610 } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
611 var offset = $el.offset();
612 form.clk_x = e.pageX - offset.left;
613 form.clk_y = e.pageY - offset.top;
614 } else {
615 form.clk_x = e.pageX - target.offsetLeft;
616 form.clk_y = e.pageY - target.offsetTop;
617 }
618 }
619 // clear form vars
620 setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
621 });
622 };
623
624 // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
625 $.fn.ajaxFormUnbind = function() {
626 return this.unbind('submit.form-plugin click.form-plugin');
627 };
628
629 /**
630 * formToArray() gathers form element data into an array of objects that can
631 * be passed to any of the following ajax functions: $.get, $.post, or load.
632 * Each object in the array has both a 'name' and 'value' property. An example of
633 * an array for a simple login form might be:
634 *
635 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
636 *
637 * It is this array that is passed to pre-submit callback functions provided to the
638 * ajaxSubmit() and ajaxForm() methods.
639 */
640 $.fn.formToArray = function(semantic) {
641 var a = [];
642 if (this.length === 0) {
643 return a;
644 }
645
646 var form = this[0];
647 var els = semantic ? form.getElementsByTagName('*') : form.elements;
648 if (!els) {
649 return a;
650 }
651
652 var i,j,n,v,el,max,jmax;
653 for(i=0, max=els.length; i < max; i++) {
654 el = els[i];
655 n = el.name;
656 if (!n) {
657 continue;
658 }
659
660 if (semantic && form.clk && el.type == "image") {
661 // handle image inputs on the fly when semantic == true
662 if(!el.disabled && form.clk == el) {
663 a.push({name: n, value: $(el).val()});
664 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
665 }
666 continue;
667 }
668
669 v = $.fieldValue(el, true);
670 if (v && v.constructor == Array) {
671 for(j=0, jmax=v.length; j < jmax; j++) {
672 a.push({name: n, value: v[j]});
673 }
674 }
675 else if (v !== null && typeof v != 'undefined') {
676 a.push({name: n, value: v});
677 }
678 }
679
680 if (!semantic && form.clk) {
681 // input type=='image' are not found in elements array! handle it here
682 var $input = $(form.clk), input = $input[0];
683 n = input.name;
684 if (n && !input.disabled && input.type == 'image') {
685 a.push({name: n, value: $input.val()});
686 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
687 }
688 }
689 return a;
690 };
691
692 /**
693 * Serializes form data into a 'submittable' string. This method will return a string
694 * in the format: name1=value1&amp;name2=value2
695 */
696 $.fn.formSerialize = function(semantic) {
697 //hand off to jQuery.param for proper encoding
698 return $.param(this.formToArray(semantic));
699 };
700
701 /**
702 * Serializes all field elements in the jQuery object into a query string.
703 * This method will return a string in the format: name1=value1&amp;name2=value2
704 */
705 $.fn.fieldSerialize = function(successful) {
706 var a = [];
707 this.each(function() {
708 var n = this.name;
709 if (!n) {
710 return;
711 }
712 var v = $.fieldValue(this, successful);
713 if (v && v.constructor == Array) {
714 for (var i=0,max=v.length; i < max; i++) {
715 a.push({name: n, value: v[i]});
716 }
717 }
718 else if (v !== null && typeof v != 'undefined') {
719 a.push({name: this.name, value: v});
720 }
721 });
722 //hand off to jQuery.param for proper encoding
723 return $.param(a);
724 };
725
726 /**
727 * Returns the value(s) of the element in the matched set. For example, consider the following form:
728 *
729 * <form><fieldset>
730 * <input name="A" type="text" />
731 * <input name="A" type="text" />
732 * <input name="B" type="checkbox" value="B1" />
733 * <input name="B" type="checkbox" value="B2"/>
734 * <input name="C" type="radio" value="C1" />
735 * <input name="C" type="radio" value="C2" />
736 * </fieldset></form>
737 *
738 * var v = $(':text').fieldValue();
739 * // if no values are entered into the text inputs
740 * v == ['','']
741 * // if values entered into the text inputs are 'foo' and 'bar'
742 * v == ['foo','bar']
743 *
744 * var v = $(':checkbox').fieldValue();
745 * // if neither checkbox is checked
746 * v === undefined
747 * // if both checkboxes are checked
748 * v == ['B1', 'B2']
749 *
750 * var v = $(':radio').fieldValue();
751 * // if neither radio is checked
752 * v === undefined
753 * // if first radio is checked
754 * v == ['C1']
755 *
756 * The successful argument controls whether or not the field element must be 'successful'
757 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
758 * The default value of the successful argument is true. If this value is false the value(s)
759 * for each element is returned.
760 *
761 * Note: This method *always* returns an array. If no valid value can be determined the
762 * array will be empty, otherwise it will contain one or more values.
763 */
764 $.fn.fieldValue = function(successful) {
765 for (var val=[], i=0, max=this.length; i < max; i++) {
766 var el = this[i];
767 var v = $.fieldValue(el, successful);
768 if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
769 continue;
770 }
771 v.constructor == Array ? $.merge(val, v) : val.push(v);
772 }
773 return val;
774 };
775
776 /**
777 * Returns the value of the field element.
778 */
779 $.fieldValue = function(el, successful) {
780 var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
781 if (successful === undefined) {
782 successful = true;
783 }
784
785 if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
786 (t == 'checkbox' || t == 'radio') && !el.checked ||
787 (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
788 tag == 'select' && el.selectedIndex == -1)) {
789 return null;
790 }
791
792 if (tag == 'select') {
793 var index = el.selectedIndex;
794 if (index < 0) {
795 return null;
796 }
797 var a = [], ops = el.options;
798 var one = (t == 'select-one');
799 var max = (one ? index+1 : ops.length);
800 for(var i=(one ? index : 0); i < max; i++) {
801 var op = ops[i];
802 if (op.selected) {
803 var v = op.value;
804 if (!v) { // extra pain for IE...
805 v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
806 }
807 if (one) {
808 return v;
809 }
810 a.push(v);
811 }
812 }
813 return a;
814 }
815 return $(el).val();
816 };
817
818 /**
819 * Clears the form data. Takes the following actions on the form's input fields:
820 * - input text fields will have their 'value' property set to the empty string
821 * - select elements will have their 'selectedIndex' property set to -1
822 * - checkbox and radio inputs will have their 'checked' property set to false
823 * - inputs of type submit, button, reset, and hidden will *not* be effected
824 * - button elements will *not* be effected
825 */
826 $.fn.clearForm = function() {
827 return this.each(function() {
828 $('input,select,textarea', this).clearFields();
829 });
830 };
831
832 /**
833 * Clears the selected form elements.
834 */
835 $.fn.clearFields = $.fn.clearInputs = function() {
836 var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
837 return this.each(function() {
838 var t = this.type, tag = this.tagName.toLowerCase();
839 if (re.test(t) || tag == 'textarea') {
840 this.value = '';
841 }
842 else if (t == 'checkbox' || t == 'radio') {
843 this.checked = false;
844 }
845 else if (tag == 'select') {
846 this.selectedIndex = -1;
847 }
848 });
849 };
850
851 /**
852 * Resets the form data. Causes all form elements to be reset to their original value.
853 */
854 $.fn.resetForm = function() {
855 return this.each(function() {
856 // guard against an input with the name of 'reset'
857 // note that IE reports the reset function as an 'object'
858 if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
859 this.reset();
860 }
861 });
862 };
863
864 /**
865 * Enables or disables any matching elements.
866 */
867 $.fn.enable = function(b) {
868 if (b === undefined) {
869 b = true;
870 }
871 return this.each(function() {
872 this.disabled = !b;
873 });
874 };
875
876 /**
877 * Checks/unchecks any matching checkboxes or radio buttons and
878 * selects/deselects and matching option elements.
879 */
880 $.fn.selected = function(select) {
881 if (select === undefined) {
882 select = true;
883 }
884 return this.each(function() {
885 var t = this.type;
886 if (t == 'checkbox' || t == 'radio') {
887 this.checked = select;
888 }
889 else if (this.tagName.toLowerCase() == 'option') {
890 var $sel = $(this).parent('select');
891 if (select && $sel[0] && $sel[0].type == 'select-one') {
892 // deselect all other options
893 $sel.find('option').selected(false);
894 }
895 this.selected = select;
896 }
897 });
898 };
899
900 // helper fn for console logging
901 function log() {
902 var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
903 if (window.console && window.console.log) {
904 window.console.log(msg);
905 }
906 else if (window.opera && window.opera.postError) {
907 window.opera.postError(msg);
908 }
909 };
910
911 })(jQuery);