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