redo gridftp navbar
[qcg-portal.git] / filex / static / filex / fileupload / jquery.fileupload.js
1 /*
2  * jQuery File Upload Plugin 5.42.3
3  * https://github.com/blueimp/jQuery-File-Upload
4  *
5  * Copyright 2010, Sebastian Tschan
6  * https://blueimp.net
7  *
8  * Licensed under the MIT license:
9  * http://www.opensource.org/licenses/MIT
10  */
11
12 /* jshint nomen:false */
13 /* global define, require, window, document, location, Blob, FormData */
14
15 (function (factory) {
16     'use strict';
17     if (typeof define === 'function' && define.amd) {
18         // Register as an anonymous AMD module:
19         define([
20             'jquery',
21             'jquery.ui.widget.js'
22         ], factory);
23     } else if (typeof exports === 'object') {
24         // Node/CommonJS:
25         factory(
26             require('jquery'),
27             require('./vendor/jquery.ui.widget')
28         );
29     } else {
30         // Browser globals:
31         factory(window.jQuery);
32     }
33 }(function ($) {
34     'use strict';
35
36     // Detect file input support, based on
37     // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
38     $.support.fileInput = !(new RegExp(
39         // Handle devices which give false positives for the feature detection:
40         '(Android (1\\.[0156]|2\\.[01]))' +
41             '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
42             '|(w(eb)?OSBrowser)|(webOS)' +
43             '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
44     ).test(window.navigator.userAgent) ||
45         // Feature detection for all other devices:
46         $('<input type="file">').prop('disabled'));
47
48     // The FileReader API is not actually used, but works as feature detection,
49     // as some Safari versions (5?) support XHR file uploads via the FormData API,
50     // but not non-multipart XHR file uploads.
51     // window.XMLHttpRequestUpload is not available on IE10, so we check for
52     // window.ProgressEvent instead to detect XHR2 file upload capability:
53     $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
54     $.support.xhrFormDataFileUpload = !!window.FormData;
55
56     // Detect support for Blob slicing (required for chunked uploads):
57     $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
58         Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
59
60     // Helper function to create drag handlers for dragover/dragenter/dragleave:
61     function getDragHandler(type) {
62         var isDragOver = type === 'dragover';
63         return function (e) {
64             e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
65             var dataTransfer = e.dataTransfer;
66             if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
67                     this._trigger(
68                         type,
69                         $.Event(type, {delegatedEvent: e})
70                     ) !== false) {
71                 e.preventDefault();
72                 if (isDragOver) {
73                     dataTransfer.dropEffect = 'copy';
74                 }
75             }
76         };
77     }
78
79     // The fileupload widget listens for change events on file input fields defined
80     // via fileInput setting and paste or drop events of the given dropZone.
81     // In addition to the default jQuery Widget methods, the fileupload widget
82     // exposes the "add" and "send" methods, to add or directly send files using
83     // the fileupload API.
84     // By default, files added via file input selection, paste, drag & drop or
85     // "add" method are uploaded immediately, but it is possible to override
86     // the "add" callback option to queue file uploads.
87     $.widget('blueimp.fileupload', {
88
89         options: {
90             // The drop target element(s), by the default the complete document.
91             // Set to null to disable drag & drop support:
92             dropZone: $(document),
93             // The paste target element(s), by the default undefined.
94             // Set to a DOM node or jQuery object to enable file pasting:
95             pasteZone: undefined,
96             // The file input field(s), that are listened to for change events.
97             // If undefined, it is set to the file input fields inside
98             // of the widget element on plugin initialization.
99             // Set to null to disable the change listener.
100             fileInput: undefined,
101             // By default, the file input field is replaced with a clone after
102             // each input field change event. This is required for iframe transport
103             // queues and allows change events to be fired for the same file
104             // selection, but can be disabled by setting the following option to false:
105             replaceFileInput: true,
106             // The parameter name for the file form data (the request argument name).
107             // If undefined or empty, the name property of the file input field is
108             // used, or "files[]" if the file input name property is also empty,
109             // can be a string or an array of strings:
110             paramName: undefined,
111             // By default, each file of a selection is uploaded using an individual
112             // request for XHR type uploads. Set to false to upload file
113             // selections in one request each:
114             singleFileUploads: true,
115             // To limit the number of files uploaded with one XHR request,
116             // set the following option to an integer greater than 0:
117             limitMultiFileUploads: undefined,
118             // The following option limits the number of files uploaded with one
119             // XHR request to keep the request size under or equal to the defined
120             // limit in bytes:
121             limitMultiFileUploadSize: undefined,
122             // Multipart file uploads add a number of bytes to each uploaded file,
123             // therefore the following option adds an overhead for each file used
124             // in the limitMultiFileUploadSize configuration:
125             limitMultiFileUploadSizeOverhead: 512,
126             // Set the following option to true to issue all file upload requests
127             // in a sequential order:
128             sequentialUploads: false,
129             // To limit the number of concurrent uploads,
130             // set the following option to an integer greater than 0:
131             limitConcurrentUploads: undefined,
132             // Set the following option to true to force iframe transport uploads:
133             forceIframeTransport: false,
134             // Set the following option to the location of a redirect url on the
135             // origin server, for cross-domain iframe transport uploads:
136             redirect: undefined,
137             // The parameter name for the redirect url, sent as part of the form
138             // data and set to 'redirect' if this option is empty:
139             redirectParamName: undefined,
140             // Set the following option to the location of a postMessage window,
141             // to enable postMessage transport uploads:
142             postMessage: undefined,
143             // By default, XHR file uploads are sent as multipart/form-data.
144             // The iframe transport is always using multipart/form-data.
145             // Set to false to enable non-multipart XHR uploads:
146             multipart: true,
147             // To upload large files in smaller chunks, set the following option
148             // to a preferred maximum chunk size. If set to 0, null or undefined,
149             // or the browser does not support the required Blob API, files will
150             // be uploaded as a whole.
151             maxChunkSize: undefined,
152             // When a non-multipart upload or a chunked multipart upload has been
153             // aborted, this option can be used to resume the upload by setting
154             // it to the size of the already uploaded bytes. This option is most
155             // useful when modifying the options object inside of the "add" or
156             // "send" callbacks, as the options are cloned for each file upload.
157             uploadedBytes: undefined,
158             // By default, failed (abort or error) file uploads are removed from the
159             // global progress calculation. Set the following option to false to
160             // prevent recalculating the global progress data:
161             recalculateProgress: true,
162             // Interval in milliseconds to calculate and trigger progress events:
163             progressInterval: 100,
164             // Interval in milliseconds to calculate progress bitrate:
165             bitrateInterval: 500,
166             // By default, uploads are started automatically when adding files:
167             autoUpload: true,
168
169             // Error and info messages:
170             messages: {
171                 uploadedBytes: 'Uploaded bytes exceed file size'
172             },
173
174             // Translation function, gets the message key to be translated
175             // and an object with context specific data as arguments:
176             i18n: function (message, context) {
177                 message = this.messages[message] || message.toString();
178                 if (context) {
179                     $.each(context, function (key, value) {
180                         message = message.replace('{' + key + '}', value);
181                     });
182                 }
183                 return message;
184             },
185
186             // Additional form data to be sent along with the file uploads can be set
187             // using this option, which accepts an array of objects with name and
188             // value properties, a function returning such an array, a FormData
189             // object (for XHR file uploads), or a simple object.
190             // The form of the first fileInput is given as parameter to the function:
191             formData: function (form) {
192                 return form.serializeArray();
193             },
194
195             // The add callback is invoked as soon as files are added to the fileupload
196             // widget (via file input selection, drag & drop, paste or add API call).
197             // If the singleFileUploads option is enabled, this callback will be
198             // called once for each file in the selection for XHR file uploads, else
199             // once for each file selection.
200             //
201             // The upload starts when the submit method is invoked on the data parameter.
202             // The data object contains a files property holding the added files
203             // and allows you to override plugin options as well as define ajax settings.
204             //
205             // Listeners for this callback can also be bound the following way:
206             // .bind('fileuploadadd', func);
207             //
208             // data.submit() returns a Promise object and allows to attach additional
209             // handlers using jQuery's Deferred callbacks:
210             // data.submit().done(func).fail(func).always(func);
211             add: function (e, data) {
212                 if (e.isDefaultPrevented()) {
213                     return false;
214                 }
215                 if (data.autoUpload || (data.autoUpload !== false &&
216                         $(this).fileupload('option', 'autoUpload'))) {
217                     data.process().done(function () {
218                         data.submit();
219                     });
220                 }
221             },
222
223             // Other callbacks:
224
225             // Callback for the submit event of each file upload:
226             // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
227
228             // Callback for the start of each file upload request:
229             // send: function (e, data) {}, // .bind('fileuploadsend', func);
230
231             // Callback for successful uploads:
232             // done: function (e, data) {}, // .bind('fileuploaddone', func);
233
234             // Callback for failed (abort or error) uploads:
235             // fail: function (e, data) {}, // .bind('fileuploadfail', func);
236
237             // Callback for completed (success, abort or error) requests:
238             // always: function (e, data) {}, // .bind('fileuploadalways', func);
239
240             // Callback for upload progress events:
241             // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
242
243             // Callback for global upload progress events:
244             // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
245
246             // Callback for uploads start, equivalent to the global ajaxStart event:
247             // start: function (e) {}, // .bind('fileuploadstart', func);
248
249             // Callback for uploads stop, equivalent to the global ajaxStop event:
250             // stop: function (e) {}, // .bind('fileuploadstop', func);
251
252             // Callback for change events of the fileInput(s):
253             // change: function (e, data) {}, // .bind('fileuploadchange', func);
254
255             // Callback for paste events to the pasteZone(s):
256             // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
257
258             // Callback for drop events of the dropZone(s):
259             // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
260
261             // Callback for dragover events of the dropZone(s):
262             // dragover: function (e) {}, // .bind('fileuploaddragover', func);
263
264             // Callback for the start of each chunk upload request:
265             // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
266
267             // Callback for successful chunk uploads:
268             // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
269
270             // Callback for failed (abort or error) chunk uploads:
271             // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
272
273             // Callback for completed (success, abort or error) chunk upload requests:
274             // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
275
276             // The plugin options are used as settings object for the ajax calls.
277             // The following are jQuery ajax settings required for the file uploads:
278             processData: false,
279             contentType: false,
280             cache: false
281         },
282
283         // A list of options that require reinitializing event listeners and/or
284         // special initialization code:
285         _specialOptions: [
286             'fileInput',
287             'dropZone',
288             'pasteZone',
289             'multipart',
290             'forceIframeTransport'
291         ],
292
293         _blobSlice: $.support.blobSlice && function () {
294             var slice = this.slice || this.webkitSlice || this.mozSlice;
295             return slice.apply(this, arguments);
296         },
297
298         _BitrateTimer: function () {
299             this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
300             this.loaded = 0;
301             this.bitrate = 0;
302             this.getBitrate = function (now, loaded, interval) {
303                 var timeDiff = now - this.timestamp;
304                 if (!this.bitrate || !interval || timeDiff > interval) {
305                     this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
306                     this.loaded = loaded;
307                     this.timestamp = now;
308                 }
309                 return this.bitrate;
310             };
311         },
312
313         _isXHRUpload: function (options) {
314             return !options.forceIframeTransport &&
315                 ((!options.multipart && $.support.xhrFileUpload) ||
316                 $.support.xhrFormDataFileUpload);
317         },
318
319         _getFormData: function (options) {
320             var formData;
321             if ($.type(options.formData) === 'function') {
322                 return options.formData(options.form);
323             }
324             if ($.isArray(options.formData)) {
325                 return options.formData;
326             }
327             if ($.type(options.formData) === 'object') {
328                 formData = [];
329                 $.each(options.formData, function (name, value) {
330                     formData.push({name: name, value: value});
331                 });
332                 return formData;
333             }
334             return [];
335         },
336
337         _getTotal: function (files) {
338             var total = 0;
339             $.each(files, function (index, file) {
340                 total += file.size || 1;
341             });
342             return total;
343         },
344
345         _initProgressObject: function (obj) {
346             var progress = {
347                 loaded: 0,
348                 total: 0,
349                 bitrate: 0
350             };
351             if (obj._progress) {
352                 $.extend(obj._progress, progress);
353             } else {
354                 obj._progress = progress;
355             }
356         },
357
358         _initResponseObject: function (obj) {
359             var prop;
360             if (obj._response) {
361                 for (prop in obj._response) {
362                     if (obj._response.hasOwnProperty(prop)) {
363                         delete obj._response[prop];
364                     }
365                 }
366             } else {
367                 obj._response = {};
368             }
369         },
370
371         _onProgress: function (e, data) {
372             if (e.lengthComputable) {
373                 var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
374                     loaded;
375                 if (data._time && data.progressInterval &&
376                         (now - data._time < data.progressInterval) &&
377                         e.loaded !== e.total) {
378                     return;
379                 }
380                 data._time = now;
381                 loaded = Math.floor(
382                     e.loaded / e.total * (data.chunkSize || data._progress.total)
383                 ) + (data.uploadedBytes || 0);
384                 // Add the difference from the previously loaded state
385                 // to the global loaded counter:
386                 this._progress.loaded += (loaded - data._progress.loaded);
387                 this._progress.bitrate = this._bitrateTimer.getBitrate(
388                     now,
389                     this._progress.loaded,
390                     data.bitrateInterval
391                 );
392                 data._progress.loaded = data.loaded = loaded;
393                 data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
394                     now,
395                     loaded,
396                     data.bitrateInterval
397                 );
398                 // Trigger a custom progress event with a total data property set
399                 // to the file size(s) of the current upload and a loaded data
400                 // property calculated accordingly:
401                 this._trigger(
402                     'progress',
403                     $.Event('progress', {delegatedEvent: e}),
404                     data
405                 );
406                 // Trigger a global progress event for all current file uploads,
407                 // including ajax calls queued for sequential file uploads:
408                 this._trigger(
409                     'progressall',
410                     $.Event('progressall', {delegatedEvent: e}),
411                     this._progress
412                 );
413             }
414         },
415
416         _initProgressListener: function (options) {
417             var that = this,
418                 xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
419             // Accesss to the native XHR object is required to add event listeners
420             // for the upload progress event:
421             if (xhr.upload) {
422                 $(xhr.upload).bind('progress', function (e) {
423                     var oe = e.originalEvent;
424                     // Make sure the progress event properties get copied over:
425                     e.lengthComputable = oe.lengthComputable;
426                     e.loaded = oe.loaded;
427                     e.total = oe.total;
428                     that._onProgress(e, options);
429                 });
430                 options.xhr = function () {
431                     return xhr;
432                 };
433             }
434         },
435
436         _isInstanceOf: function (type, obj) {
437             // Cross-frame instanceof check
438             return Object.prototype.toString.call(obj) === '[object ' + type + ']';
439         },
440
441         _initXHRData: function (options) {
442             var that = this,
443                 formData,
444                 file = options.files[0],
445                 // Ignore non-multipart setting if not supported:
446                 multipart = options.multipart || !$.support.xhrFileUpload,
447                 paramName = $.type(options.paramName) === 'array' ?
448                     options.paramName[0] : options.paramName;
449             options.headers = $.extend({}, options.headers);
450             if (options.contentRange) {
451                 options.headers['Content-Range'] = options.contentRange;
452             }
453             if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
454                 options.headers['Content-Disposition'] = 'attachment; filename="' +
455                     encodeURI(file.name) + '"';
456             }
457             if (!multipart) {
458                 options.contentType = file.type || 'application/octet-stream';
459                 options.data = options.blob || file;
460             } else if ($.support.xhrFormDataFileUpload) {
461                 if (options.postMessage) {
462                     // window.postMessage does not allow sending FormData
463                     // objects, so we just add the File/Blob objects to
464                     // the formData array and let the postMessage window
465                     // create the FormData object out of this array:
466                     formData = this._getFormData(options);
467                     if (options.blob) {
468                         formData.push({
469                             name: paramName,
470                             value: options.blob
471                         });
472                     } else {
473                         $.each(options.files, function (index, file) {
474                             formData.push({
475                                 name: ($.type(options.paramName) === 'array' &&
476                                     options.paramName[index]) || paramName,
477                                 value: file
478                             });
479                         });
480                     }
481                 } else {
482                     if (that._isInstanceOf('FormData', options.formData)) {
483                         formData = options.formData;
484                     } else {
485                         formData = new FormData();
486                         $.each(this._getFormData(options), function (index, field) {
487                             formData.append(field.name, field.value);
488                         });
489                     }
490                     if (options.blob) {
491                         formData.append(paramName, options.blob, file.name);
492                     } else {
493                         $.each(options.files, function (index, file) {
494                             // This check allows the tests to run with
495                             // dummy objects:
496                             if (that._isInstanceOf('File', file) ||
497                                     that._isInstanceOf('Blob', file)) {
498                                 formData.append(
499                                     ($.type(options.paramName) === 'array' &&
500                                         options.paramName[index]) || paramName,
501                                     file,
502                                     file.uploadName || file.name
503                                 );
504                             }
505                         });
506                     }
507                 }
508                 options.data = formData;
509             }
510             // Blob reference is not needed anymore, free memory:
511             options.blob = null;
512         },
513
514         _initIframeSettings: function (options) {
515             var targetHost = $('<a></a>').prop('href', options.url).prop('host');
516             // Setting the dataType to iframe enables the iframe transport:
517             options.dataType = 'iframe ' + (options.dataType || '');
518             // The iframe transport accepts a serialized array as form data:
519             options.formData = this._getFormData(options);
520             // Add redirect url to form data on cross-domain uploads:
521             if (options.redirect && targetHost && targetHost !== location.host) {
522                 options.formData.push({
523                     name: options.redirectParamName || 'redirect',
524                     value: options.redirect
525                 });
526             }
527         },
528
529         _initDataSettings: function (options) {
530             if (this._isXHRUpload(options)) {
531                 if (!this._chunkedUpload(options, true)) {
532                     if (!options.data) {
533                         this._initXHRData(options);
534                     }
535                     this._initProgressListener(options);
536                 }
537                 if (options.postMessage) {
538                     // Setting the dataType to postmessage enables the
539                     // postMessage transport:
540                     options.dataType = 'postmessage ' + (options.dataType || '');
541                 }
542             } else {
543                 this._initIframeSettings(options);
544             }
545         },
546
547         _getParamName: function (options) {
548             var fileInput = $(options.fileInput),
549                 paramName = options.paramName;
550             if (!paramName) {
551                 paramName = [];
552                 fileInput.each(function () {
553                     var input = $(this),
554                         name = input.prop('name') || 'files[]',
555                         i = (input.prop('files') || [1]).length;
556                     while (i) {
557                         paramName.push(name);
558                         i -= 1;
559                     }
560                 });
561                 if (!paramName.length) {
562                     paramName = [fileInput.prop('name') || 'files[]'];
563                 }
564             } else if (!$.isArray(paramName)) {
565                 paramName = [paramName];
566             }
567             return paramName;
568         },
569
570         _initFormSettings: function (options) {
571             // Retrieve missing options from the input field and the
572             // associated form, if available:
573             if (!options.form || !options.form.length) {
574                 options.form = $(options.fileInput.prop('form'));
575                 // If the given file input doesn't have an associated form,
576                 // use the default widget file input's form:
577                 if (!options.form.length) {
578                     options.form = $(this.options.fileInput.prop('form'));
579                 }
580             }
581             options.paramName = this._getParamName(options);
582             if (!options.url) {
583                 options.url = options.form.prop('action') || location.href;
584             }
585             // The HTTP request method must be "POST" or "PUT":
586             options.type = (options.type ||
587                 ($.type(options.form.prop('method')) === 'string' &&
588                     options.form.prop('method')) || ''
589                 ).toUpperCase();
590             if (options.type !== 'POST' && options.type !== 'PUT' &&
591                     options.type !== 'PATCH') {
592                 options.type = 'POST';
593             }
594             if (!options.formAcceptCharset) {
595                 options.formAcceptCharset = options.form.attr('accept-charset');
596             }
597         },
598
599         _getAJAXSettings: function (data) {
600             var options = $.extend({}, this.options, data);
601             this._initFormSettings(options);
602             this._initDataSettings(options);
603             return options;
604         },
605
606         // jQuery 1.6 doesn't provide .state(),
607         // while jQuery 1.8+ removed .isRejected() and .isResolved():
608         _getDeferredState: function (deferred) {
609             if (deferred.state) {
610                 return deferred.state();
611             }
612             if (deferred.isResolved()) {
613                 return 'resolved';
614             }
615             if (deferred.isRejected()) {
616                 return 'rejected';
617             }
618             return 'pending';
619         },
620
621         // Maps jqXHR callbacks to the equivalent
622         // methods of the given Promise object:
623         _enhancePromise: function (promise) {
624             promise.success = promise.done;
625             promise.error = promise.fail;
626             promise.complete = promise.always;
627             return promise;
628         },
629
630         // Creates and returns a Promise object enhanced with
631         // the jqXHR methods abort, success, error and complete:
632         _getXHRPromise: function (resolveOrReject, context, args) {
633             var dfd = $.Deferred(),
634                 promise = dfd.promise();
635             context = context || this.options.context || promise;
636             if (resolveOrReject === true) {
637                 dfd.resolveWith(context, args);
638             } else if (resolveOrReject === false) {
639                 dfd.rejectWith(context, args);
640             }
641             promise.abort = dfd.promise;
642             return this._enhancePromise(promise);
643         },
644
645         // Adds convenience methods to the data callback argument:
646         _addConvenienceMethods: function (e, data) {
647             var that = this,
648                 getPromise = function (args) {
649                     return $.Deferred().resolveWith(that, args).promise();
650                 };
651             data.process = function (resolveFunc, rejectFunc) {
652                 if (resolveFunc || rejectFunc) {
653                     data._processQueue = this._processQueue =
654                         (this._processQueue || getPromise([this])).pipe(
655                             function () {
656                                 if (data.errorThrown) {
657                                     return $.Deferred()
658                                         .rejectWith(that, [data]).promise();
659                                 }
660                                 return getPromise(arguments);
661                             }
662                         ).pipe(resolveFunc, rejectFunc);
663                 }
664                 return this._processQueue || getPromise([this]);
665             };
666             data.submit = function () {
667                 if (this.state() !== 'pending') {
668                     data.jqXHR = this.jqXHR =
669                         (that._trigger(
670                             'submit',
671                             $.Event('submit', {delegatedEvent: e}),
672                             this
673                         ) !== false) && that._onSend(e, this);
674                 }
675                 return this.jqXHR || that._getXHRPromise();
676             };
677             data.abort = function () {
678                 if (this.jqXHR) {
679                     return this.jqXHR.abort();
680                 }
681                 this.errorThrown = 'abort';
682                 that._trigger('fail', null, this);
683                 return that._getXHRPromise(false);
684             };
685             data.state = function () {
686                 if (this.jqXHR) {
687                     return that._getDeferredState(this.jqXHR);
688                 }
689                 if (this._processQueue) {
690                     return that._getDeferredState(this._processQueue);
691                 }
692             };
693             data.processing = function () {
694                 return !this.jqXHR && this._processQueue && that
695                     ._getDeferredState(this._processQueue) === 'pending';
696             };
697             data.progress = function () {
698                 return this._progress;
699             };
700             data.response = function () {
701                 return this._response;
702             };
703         },
704
705         // Parses the Range header from the server response
706         // and returns the uploaded bytes:
707         _getUploadedBytes: function (jqXHR) {
708             var range = jqXHR.getResponseHeader('Range'),
709                 parts = range && range.split('-'),
710                 upperBytesPos = parts && parts.length > 1 &&
711                     parseInt(parts[1], 10);
712             return upperBytesPos && upperBytesPos + 1;
713         },
714
715         // Uploads a file in multiple, sequential requests
716         // by splitting the file up in multiple blob chunks.
717         // If the second parameter is true, only tests if the file
718         // should be uploaded in chunks, but does not invoke any
719         // upload requests:
720         _chunkedUpload: function (options, testOnly) {
721             options.uploadedBytes = options.uploadedBytes || 0;
722             var that = this,
723                 file = options.files[0],
724                 fs = file.size,
725                 ub = options.uploadedBytes,
726                 mcs = options.maxChunkSize || fs,
727                 slice = this._blobSlice,
728                 dfd = $.Deferred(),
729                 promise = dfd.promise(),
730                 jqXHR,
731                 upload;
732             if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
733                     options.data) {
734                 return false;
735             }
736             if (testOnly) {
737                 return true;
738             }
739             if (ub >= fs) {
740                 file.error = options.i18n('uploadedBytes');
741                 return this._getXHRPromise(
742                     false,
743                     options.context,
744                     [null, 'error', file.error]
745                 );
746             }
747             // The chunk upload method:
748             upload = function () {
749                 // Clone the options object for each chunk upload:
750                 var o = $.extend({}, options),
751                     currentLoaded = o._progress.loaded;
752                 o.blob = slice.call(
753                     file,
754                     ub,
755                     ub + mcs,
756                     file.type
757                 );
758                 // Store the current chunk size, as the blob itself
759                 // will be dereferenced after data processing:
760                 o.chunkSize = o.blob.size;
761                 // Expose the chunk bytes position range:
762                 o.contentRange = 'bytes ' + ub + '-' +
763                     (ub + o.chunkSize - 1) + '/' + fs;
764                 // Process the upload data (the blob and potential form data):
765                 that._initXHRData(o);
766                 // Add progress listeners for this chunk upload:
767                 that._initProgressListener(o);
768                 jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
769                         that._getXHRPromise(false, o.context))
770                     .done(function (result, textStatus, jqXHR) {
771                         ub = that._getUploadedBytes(jqXHR) ||
772                             (ub + o.chunkSize);
773                         // Create a progress event if no final progress event
774                         // with loaded equaling total has been triggered
775                         // for this chunk:
776                         if (currentLoaded + o.chunkSize - o._progress.loaded) {
777                             that._onProgress($.Event('progress', {
778                                 lengthComputable: true,
779                                 loaded: ub - o.uploadedBytes,
780                                 total: ub - o.uploadedBytes
781                             }), o);
782                         }
783                         options.uploadedBytes = o.uploadedBytes = ub;
784                         o.result = result;
785                         o.textStatus = textStatus;
786                         o.jqXHR = jqXHR;
787                         that._trigger('chunkdone', null, o);
788                         that._trigger('chunkalways', null, o);
789                         if (ub < fs) {
790                             // File upload not yet complete,
791                             // continue with the next chunk:
792                             upload();
793                         } else {
794                             dfd.resolveWith(
795                                 o.context,
796                                 [result, textStatus, jqXHR]
797                             );
798                         }
799                     })
800                     .fail(function (jqXHR, textStatus, errorThrown) {
801                         o.jqXHR = jqXHR;
802                         o.textStatus = textStatus;
803                         o.errorThrown = errorThrown;
804                         that._trigger('chunkfail', null, o);
805                         that._trigger('chunkalways', null, o);
806                         dfd.rejectWith(
807                             o.context,
808                             [jqXHR, textStatus, errorThrown]
809                         );
810                     });
811             };
812             this._enhancePromise(promise);
813             promise.abort = function () {
814                 return jqXHR.abort();
815             };
816             upload();
817             return promise;
818         },
819
820         _beforeSend: function (e, data) {
821             if (this._active === 0) {
822                 // the start callback is triggered when an upload starts
823                 // and no other uploads are currently running,
824                 // equivalent to the global ajaxStart event:
825                 this._trigger('start');
826                 // Set timer for global bitrate progress calculation:
827                 this._bitrateTimer = new this._BitrateTimer();
828                 // Reset the global progress values:
829                 this._progress.loaded = this._progress.total = 0;
830                 this._progress.bitrate = 0;
831             }
832             // Make sure the container objects for the .response() and
833             // .progress() methods on the data object are available
834             // and reset to their initial state:
835             this._initResponseObject(data);
836             this._initProgressObject(data);
837             data._progress.loaded = data.loaded = data.uploadedBytes || 0;
838             data._progress.total = data.total = this._getTotal(data.files) || 1;
839             data._progress.bitrate = data.bitrate = 0;
840             this._active += 1;
841             // Initialize the global progress values:
842             this._progress.loaded += data.loaded;
843             this._progress.total += data.total;
844         },
845
846         _onDone: function (result, textStatus, jqXHR, options) {
847             var total = options._progress.total,
848                 response = options._response;
849             if (options._progress.loaded < total) {
850                 // Create a progress event if no final progress event
851                 // with loaded equaling total has been triggered:
852                 this._onProgress($.Event('progress', {
853                     lengthComputable: true,
854                     loaded: total,
855                     total: total
856                 }), options);
857             }
858             response.result = options.result = result;
859             response.textStatus = options.textStatus = textStatus;
860             response.jqXHR = options.jqXHR = jqXHR;
861             this._trigger('done', null, options);
862         },
863
864         _onFail: function (jqXHR, textStatus, errorThrown, options) {
865             var response = options._response;
866             if (options.recalculateProgress) {
867                 // Remove the failed (error or abort) file upload from
868                 // the global progress calculation:
869                 this._progress.loaded -= options._progress.loaded;
870                 this._progress.total -= options._progress.total;
871             }
872             response.jqXHR = options.jqXHR = jqXHR;
873             response.textStatus = options.textStatus = textStatus;
874             response.errorThrown = options.errorThrown = errorThrown;
875             this._trigger('fail', null, options);
876         },
877
878         _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
879             // jqXHRorResult, textStatus and jqXHRorError are added to the
880             // options object via done and fail callbacks
881             this._trigger('always', null, options);
882         },
883
884         _onSend: function (e, data) {
885             if (!data.submit) {
886                 this._addConvenienceMethods(e, data);
887             }
888             var that = this,
889                 jqXHR,
890                 aborted,
891                 slot,
892                 pipe,
893                 options = that._getAJAXSettings(data),
894                 send = function () {
895                     that._sending += 1;
896                     // Set timer for bitrate progress calculation:
897                     options._bitrateTimer = new that._BitrateTimer();
898                     jqXHR = jqXHR || (
899                         ((aborted || that._trigger(
900                             'send',
901                             $.Event('send', {delegatedEvent: e}),
902                             options
903                         ) === false) &&
904                         that._getXHRPromise(false, options.context, aborted)) ||
905                         that._chunkedUpload(options) || $.ajax(options)
906                     ).done(function (result, textStatus, jqXHR) {
907                         that._onDone(result, textStatus, jqXHR, options);
908                     }).fail(function (jqXHR, textStatus, errorThrown) {
909                         that._onFail(jqXHR, textStatus, errorThrown, options);
910                     }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
911                         that._onAlways(
912                             jqXHRorResult,
913                             textStatus,
914                             jqXHRorError,
915                             options
916                         );
917                         that._sending -= 1;
918                         that._active -= 1;
919                         if (options.limitConcurrentUploads &&
920                                 options.limitConcurrentUploads > that._sending) {
921                             // Start the next queued upload,
922                             // that has not been aborted:
923                             var nextSlot = that._slots.shift();
924                             while (nextSlot) {
925                                 if (that._getDeferredState(nextSlot) === 'pending') {
926                                     nextSlot.resolve();
927                                     break;
928                                 }
929                                 nextSlot = that._slots.shift();
930                             }
931                         }
932                         if (that._active === 0) {
933                             // The stop callback is triggered when all uploads have
934                             // been completed, equivalent to the global ajaxStop event:
935                             that._trigger('stop');
936                         }
937                     });
938                     return jqXHR;
939                 };
940             this._beforeSend(e, options);
941             if (this.options.sequentialUploads ||
942                     (this.options.limitConcurrentUploads &&
943                     this.options.limitConcurrentUploads <= this._sending)) {
944                 if (this.options.limitConcurrentUploads > 1) {
945                     slot = $.Deferred();
946                     this._slots.push(slot);
947                     pipe = slot.pipe(send);
948                 } else {
949                     this._sequence = this._sequence.pipe(send, send);
950                     pipe = this._sequence;
951                 }
952                 // Return the piped Promise object, enhanced with an abort method,
953                 // which is delegated to the jqXHR object of the current upload,
954                 // and jqXHR callbacks mapped to the equivalent Promise methods:
955                 pipe.abort = function () {
956                     aborted = [undefined, 'abort', 'abort'];
957                     if (!jqXHR) {
958                         if (slot) {
959                             slot.rejectWith(options.context, aborted);
960                         }
961                         return send();
962                     }
963                     return jqXHR.abort();
964                 };
965                 return this._enhancePromise(pipe);
966             }
967             return send();
968         },
969
970         _onAdd: function (e, data) {
971             var that = this,
972                 result = true,
973                 options = $.extend({}, this.options, data),
974                 files = data.files,
975                 filesLength = files.length,
976                 limit = options.limitMultiFileUploads,
977                 limitSize = options.limitMultiFileUploadSize,
978                 overhead = options.limitMultiFileUploadSizeOverhead,
979                 batchSize = 0,
980                 paramName = this._getParamName(options),
981                 paramNameSet,
982                 paramNameSlice,
983                 fileSet,
984                 i,
985                 j = 0;
986             if (limitSize && (!filesLength || files[0].size === undefined)) {
987                 limitSize = undefined;
988             }
989             if (!(options.singleFileUploads || limit || limitSize) ||
990                     !this._isXHRUpload(options)) {
991                 fileSet = [files];
992                 paramNameSet = [paramName];
993             } else if (!(options.singleFileUploads || limitSize) && limit) {
994                 fileSet = [];
995                 paramNameSet = [];
996                 for (i = 0; i < filesLength; i += limit) {
997                     fileSet.push(files.slice(i, i + limit));
998                     paramNameSlice = paramName.slice(i, i + limit);
999                     if (!paramNameSlice.length) {
1000                         paramNameSlice = paramName;
1001                     }
1002                     paramNameSet.push(paramNameSlice);
1003                 }
1004             } else if (!options.singleFileUploads && limitSize) {
1005                 fileSet = [];
1006                 paramNameSet = [];
1007                 for (i = 0; i < filesLength; i = i + 1) {
1008                     batchSize += files[i].size + overhead;
1009                     if (i + 1 === filesLength ||
1010                             ((batchSize + files[i + 1].size + overhead) > limitSize) ||
1011                             (limit && i + 1 - j >= limit)) {
1012                         fileSet.push(files.slice(j, i + 1));
1013                         paramNameSlice = paramName.slice(j, i + 1);
1014                         if (!paramNameSlice.length) {
1015                             paramNameSlice = paramName;
1016                         }
1017                         paramNameSet.push(paramNameSlice);
1018                         j = i + 1;
1019                         batchSize = 0;
1020                     }
1021                 }
1022             } else {
1023                 paramNameSet = paramName;
1024             }
1025             data.originalFiles = files;
1026             $.each(fileSet || files, function (index, element) {
1027                 var newData = $.extend({}, data);
1028                 newData.files = fileSet ? element : [element];
1029                 newData.paramName = paramNameSet[index];
1030                 that._initResponseObject(newData);
1031                 that._initProgressObject(newData);
1032                 that._addConvenienceMethods(e, newData);
1033                 result = that._trigger(
1034                     'add',
1035                     $.Event('add', {delegatedEvent: e}),
1036                     newData
1037                 );
1038                 return result;
1039             });
1040             return result;
1041         },
1042
1043         _replaceFileInput: function (data) {
1044             var input = data.fileInput,
1045                 inputClone = input.clone(true);
1046             // Add a reference for the new cloned file input to the data argument:
1047             data.fileInputClone = inputClone;
1048             $('<form></form>').append(inputClone)[0].reset();
1049             // Detaching allows to insert the fileInput on another form
1050             // without loosing the file input value:
1051             input.after(inputClone).detach();
1052             // Avoid memory leaks with the detached file input:
1053             $.cleanData(input.unbind('remove'));
1054             // Replace the original file input element in the fileInput
1055             // elements set with the clone, which has been copied including
1056             // event handlers:
1057             this.options.fileInput = this.options.fileInput.map(function (i, el) {
1058                 if (el === input[0]) {
1059                     return inputClone[0];
1060                 }
1061                 return el;
1062             });
1063             // If the widget has been initialized on the file input itself,
1064             // override this.element with the file input clone:
1065             if (input[0] === this.element[0]) {
1066                 this.element = inputClone;
1067             }
1068         },
1069
1070         _handleFileTreeEntry: function (entry, path) {
1071             var that = this,
1072                 dfd = $.Deferred(),
1073                 errorHandler = function (e) {
1074                     if (e && !e.entry) {
1075                         e.entry = entry;
1076                     }
1077                     // Since $.when returns immediately if one
1078                     // Deferred is rejected, we use resolve instead.
1079                     // This allows valid files and invalid items
1080                     // to be returned together in one set:
1081                     dfd.resolve([e]);
1082                 },
1083                 successHandler = function (entries) {
1084                     that._handleFileTreeEntries(
1085                         entries,
1086                         path + entry.name + '/'
1087                     ).done(function (files) {
1088                         dfd.resolve(files);
1089                     }).fail(errorHandler);
1090                 },
1091                 readEntries = function () {
1092                     dirReader.readEntries(function (results) {
1093                         if (!results.length) {
1094                             successHandler(entries);
1095                         } else {
1096                             entries = entries.concat(results);
1097                             readEntries();
1098                         }
1099                     }, errorHandler);
1100                 },
1101                 dirReader, entries = [];
1102             path = path || '';
1103             if (entry.isFile) {
1104                 if (entry._file) {
1105                     // Workaround for Chrome bug #149735
1106                     entry._file.relativePath = path;
1107                     dfd.resolve(entry._file);
1108                 } else {
1109                     entry.file(function (file) {
1110                         file.relativePath = path;
1111                         dfd.resolve(file);
1112                     }, errorHandler);
1113                 }
1114             } else if (entry.isDirectory) {
1115                 dirReader = entry.createReader();
1116                 readEntries();
1117             } else {
1118                 // Return an empy list for file system items
1119                 // other than files or directories:
1120                 dfd.resolve([]);
1121             }
1122             return dfd.promise();
1123         },
1124
1125         _handleFileTreeEntries: function (entries, path) {
1126             var that = this;
1127             return $.when.apply(
1128                 $,
1129                 $.map(entries, function (entry) {
1130                     return that._handleFileTreeEntry(entry, path);
1131                 })
1132             ).pipe(function () {
1133                 return Array.prototype.concat.apply(
1134                     [],
1135                     arguments
1136                 );
1137             });
1138         },
1139
1140         _getDroppedFiles: function (dataTransfer) {
1141             dataTransfer = dataTransfer || {};
1142             var items = dataTransfer.items;
1143             if (items && items.length && (items[0].webkitGetAsEntry ||
1144                     items[0].getAsEntry)) {
1145                 return this._handleFileTreeEntries(
1146                     $.map(items, function (item) {
1147                         var entry;
1148                         if (item.webkitGetAsEntry) {
1149                             entry = item.webkitGetAsEntry();
1150                             if (entry) {
1151                                 // Workaround for Chrome bug #149735:
1152                                 entry._file = item.getAsFile();
1153                             }
1154                             return entry;
1155                         }
1156                         return item.getAsEntry();
1157                     })
1158                 );
1159             }
1160             return $.Deferred().resolve(
1161                 $.makeArray(dataTransfer.files)
1162             ).promise();
1163         },
1164
1165         _getSingleFileInputFiles: function (fileInput) {
1166             fileInput = $(fileInput);
1167             var entries = fileInput.prop('webkitEntries') ||
1168                     fileInput.prop('entries'),
1169                 files,
1170                 value;
1171             if (entries && entries.length) {
1172                 return this._handleFileTreeEntries(entries);
1173             }
1174             files = $.makeArray(fileInput.prop('files'));
1175             if (!files.length) {
1176                 value = fileInput.prop('value');
1177                 if (!value) {
1178                     return $.Deferred().resolve([]).promise();
1179                 }
1180                 // If the files property is not available, the browser does not
1181                 // support the File API and we add a pseudo File object with
1182                 // the input value as name with path information removed:
1183                 files = [{name: value.replace(/^.*\\/, '')}];
1184             } else if (files[0].name === undefined && files[0].fileName) {
1185                 // File normalization for Safari 4 and Firefox 3:
1186                 $.each(files, function (index, file) {
1187                     file.name = file.fileName;
1188                     file.size = file.fileSize;
1189                 });
1190             }
1191             return $.Deferred().resolve(files).promise();
1192         },
1193
1194         _getFileInputFiles: function (fileInput) {
1195             if (!(fileInput instanceof $) || fileInput.length === 1) {
1196                 return this._getSingleFileInputFiles(fileInput);
1197             }
1198             return $.when.apply(
1199                 $,
1200                 $.map(fileInput, this._getSingleFileInputFiles)
1201             ).pipe(function () {
1202                 return Array.prototype.concat.apply(
1203                     [],
1204                     arguments
1205                 );
1206             });
1207         },
1208
1209         _onChange: function (e) {
1210             var that = this,
1211                 data = {
1212                     fileInput: $(e.target),
1213                     form: $(e.target.form)
1214                 };
1215             this._getFileInputFiles(data.fileInput).always(function (files) {
1216                 data.files = files;
1217                 if (that.options.replaceFileInput) {
1218                     that._replaceFileInput(data);
1219                 }
1220                 if (that._trigger(
1221                         'change',
1222                         $.Event('change', {delegatedEvent: e}),
1223                         data
1224                     ) !== false) {
1225                     that._onAdd(e, data);
1226                 }
1227             });
1228         },
1229
1230         _onPaste: function (e) {
1231             var items = e.originalEvent && e.originalEvent.clipboardData &&
1232                     e.originalEvent.clipboardData.items,
1233                 data = {files: []};
1234             if (items && items.length) {
1235                 $.each(items, function (index, item) {
1236                     var file = item.getAsFile && item.getAsFile();
1237                     if (file) {
1238                         data.files.push(file);
1239                     }
1240                 });
1241                 if (this._trigger(
1242                         'paste',
1243                         $.Event('paste', {delegatedEvent: e}),
1244                         data
1245                     ) !== false) {
1246                     this._onAdd(e, data);
1247                 }
1248             }
1249         },
1250
1251         _onDrop: function (e) {
1252             e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1253             var that = this,
1254                 dataTransfer = e.dataTransfer,
1255                 data = {};
1256             if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1257                 e.preventDefault();
1258                 this._getDroppedFiles(dataTransfer).always(function (files) {
1259                     data.files = files;
1260                     if (that._trigger(
1261                             'drop',
1262                             $.Event('drop', {delegatedEvent: e}),
1263                             data
1264                         ) !== false) {
1265                         that._onAdd(e, data);
1266                     }
1267                 });
1268             }
1269         },
1270
1271         _onDragOver: getDragHandler('dragover'),
1272
1273         _onDragEnter: getDragHandler('dragenter'),
1274
1275         _onDragLeave: getDragHandler('dragleave'),
1276
1277         _initEventHandlers: function () {
1278             if (this._isXHRUpload(this.options)) {
1279                 this._on(this.options.dropZone, {
1280                     dragover: this._onDragOver,
1281                     drop: this._onDrop,
1282                     // event.preventDefault() on dragenter is required for IE10+:
1283                     dragenter: this._onDragEnter,
1284                     // dragleave is not required, but added for completeness:
1285                     dragleave: this._onDragLeave
1286                 });
1287                 this._on(this.options.pasteZone, {
1288                     paste: this._onPaste
1289                 });
1290             }
1291             if ($.support.fileInput) {
1292                 this._on(this.options.fileInput, {
1293                     change: this._onChange
1294                 });
1295             }
1296         },
1297
1298         _destroyEventHandlers: function () {
1299             this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
1300             this._off(this.options.pasteZone, 'paste');
1301             this._off(this.options.fileInput, 'change');
1302         },
1303
1304         _setOption: function (key, value) {
1305             var reinit = $.inArray(key, this._specialOptions) !== -1;
1306             if (reinit) {
1307                 this._destroyEventHandlers();
1308             }
1309             this._super(key, value);
1310             if (reinit) {
1311                 this._initSpecialOptions();
1312                 this._initEventHandlers();
1313             }
1314         },
1315
1316         _initSpecialOptions: function () {
1317             var options = this.options;
1318             if (options.fileInput === undefined) {
1319                 options.fileInput = this.element.is('input[type="file"]') ?
1320                         this.element : this.element.find('input[type="file"]');
1321             } else if (!(options.fileInput instanceof $)) {
1322                 options.fileInput = $(options.fileInput);
1323             }
1324             if (!(options.dropZone instanceof $)) {
1325                 options.dropZone = $(options.dropZone);
1326             }
1327             if (!(options.pasteZone instanceof $)) {
1328                 options.pasteZone = $(options.pasteZone);
1329             }
1330         },
1331
1332         _getRegExp: function (str) {
1333             var parts = str.split('/'),
1334                 modifiers = parts.pop();
1335             parts.shift();
1336             return new RegExp(parts.join('/'), modifiers);
1337         },
1338
1339         _isRegExpOption: function (key, value) {
1340             return key !== 'url' && $.type(value) === 'string' &&
1341                 /^\/.*\/[igm]{0,3}$/.test(value);
1342         },
1343
1344         _initDataAttributes: function () {
1345             var that = this,
1346                 options = this.options,
1347                 data = this.element.data();
1348             // Initialize options set via HTML5 data-attributes:
1349             $.each(
1350                 this.element[0].attributes,
1351                 function (index, attr) {
1352                     var key = attr.name.toLowerCase(),
1353                         value;
1354                     if (/^data-/.test(key)) {
1355                         // Convert hyphen-ated key to camelCase:
1356                         key = key.slice(5).replace(/-[a-z]/g, function (str) {
1357                             return str.charAt(1).toUpperCase();
1358                         });
1359                         value = data[key];
1360                         if (that._isRegExpOption(key, value)) {
1361                             value = that._getRegExp(value);
1362                         }
1363                         options[key] = value;
1364                     }
1365                 }
1366             );
1367         },
1368
1369         _create: function () {
1370             this._initDataAttributes();
1371             this._initSpecialOptions();
1372             this._slots = [];
1373             this._sequence = this._getXHRPromise(true);
1374             this._sending = this._active = 0;
1375             this._initProgressObject(this);
1376             this._initEventHandlers();
1377         },
1378
1379         // This method is exposed to the widget API and allows to query
1380         // the number of active uploads:
1381         active: function () {
1382             return this._active;
1383         },
1384
1385         // This method is exposed to the widget API and allows to query
1386         // the widget upload progress.
1387         // It returns an object with loaded, total and bitrate properties
1388         // for the running uploads:
1389         progress: function () {
1390             return this._progress;
1391         },
1392
1393         // This method is exposed to the widget API and allows adding files
1394         // using the fileupload API. The data parameter accepts an object which
1395         // must have a files property and can contain additional options:
1396         // .fileupload('add', {files: filesList});
1397         add: function (data) {
1398             var that = this;
1399             if (!data || this.options.disabled) {
1400                 return;
1401             }
1402             if (data.fileInput && !data.files) {
1403                 this._getFileInputFiles(data.fileInput).always(function (files) {
1404                     data.files = files;
1405                     that._onAdd(null, data);
1406                 });
1407             } else {
1408                 data.files = $.makeArray(data.files);
1409                 this._onAdd(null, data);
1410             }
1411         },
1412
1413         // This method is exposed to the widget API and allows sending files
1414         // using the fileupload API. The data parameter accepts an object which
1415         // must have a files or fileInput property and can contain additional options:
1416         // .fileupload('send', {files: filesList});
1417         // The method returns a Promise object for the file upload call.
1418         send: function (data) {
1419             if (data && !this.options.disabled) {
1420                 if (data.fileInput && !data.files) {
1421                     var that = this,
1422                         dfd = $.Deferred(),
1423                         promise = dfd.promise(),
1424                         jqXHR,
1425                         aborted;
1426                     promise.abort = function () {
1427                         aborted = true;
1428                         if (jqXHR) {
1429                             return jqXHR.abort();
1430                         }
1431                         dfd.reject(null, 'abort', 'abort');
1432                         return promise;
1433                     };
1434                     this._getFileInputFiles(data.fileInput).always(
1435                         function (files) {
1436                             if (aborted) {
1437                                 return;
1438                             }
1439                             if (!files.length) {
1440                                 dfd.reject();
1441                                 return;
1442                             }
1443                             data.files = files;
1444                             jqXHR = that._onSend(null, data);
1445                             jqXHR.then(
1446                                 function (result, textStatus, jqXHR) {
1447                                     dfd.resolve(result, textStatus, jqXHR);
1448                                 },
1449                                 function (jqXHR, textStatus, errorThrown) {
1450                                     dfd.reject(jqXHR, textStatus, errorThrown);
1451                                 }
1452                             );
1453                         }
1454                     );
1455                     return this._enhancePromise(promise);
1456                 }
1457                 data.files = $.makeArray(data.files);
1458                 if (data.files.length) {
1459                     return this._onSend(null, data);
1460                 }
1461             }
1462             return this._getXHRPromise(false, data && data.context);
1463         }
1464
1465     });
1466
1467 }));