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