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