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