PluginProbe
WpStream – Live Streaming, Video on Demand, Pay Per View / 3.2.2
WpStream – Live Streaming, Video on Demand, Pay Per View v3.2.2
4.14.1 4.14.0 4.13.2 4.13.1 4.13 4.12.5 4.12.4 4.12.3 4.12.2 4.12.1 4.12 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5 4.5.1 4.5.11 4.5.11.1 4.5.11.2 4.5.11.4 4.5.11.5 4.5.11.6 All 181 releases
wpstream / admin / js / jquery.fileupload.js

jquery.fileupload.js in WpStream – Live Streaming, Video on Demand, Pay Per View 3.2.2, at admin/js/jquery.fileupload.js

1,503 lines 63.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * jQuery File Upload Plugin
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 * https://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/ui/widget'
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 before the start of each chunk upload request (before form data initialization):
265 // chunkbeforesend: function (e, data) {}, // .bind('fileuploadchunkbeforesend', func);
266
267 // Callback for the start of each chunk upload request:
268 // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
269
270 // Callback for successful chunk uploads:
271 // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
272
273 // Callback for failed (abort or error) chunk uploads:
274 // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
275
276 // Callback for completed (success, abort or error) chunk upload requests:
277 // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
278
279 // The plugin options are used as settings object for the ajax calls.
280 // The following are jQuery ajax settings required for the file uploads:
281 processData: false,
282 contentType: false,
283 cache: false,
284 timeout: 0
285 },
286
287 // A list of options that require reinitializing event listeners and/or
288 // special initialization code:
289 _specialOptions: [
290 'fileInput',
291 'dropZone',
292 'pasteZone',
293 'multipart',
294 'forceIframeTransport'
295 ],
296
297 _blobSlice: $.support.blobSlice && function () {
298 var slice = this.slice || this.webkitSlice || this.mozSlice;
299 return slice.apply(this, arguments);
300 },
301
302 _BitrateTimer: function () {
303 this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
304 this.loaded = 0;
305 this.bitrate = 0;
306 this.getBitrate = function (now, loaded, interval) {
307 var timeDiff = now - this.timestamp;
308 if (!this.bitrate || !interval || timeDiff > interval) {
309 this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
310 this.loaded = loaded;
311 this.timestamp = now;
312 }
313 return this.bitrate;
314 };
315 },
316
317 _isXHRUpload: function (options) {
318 return !options.forceIframeTransport &&
319 ((!options.multipart && $.support.xhrFileUpload) ||
320 $.support.xhrFormDataFileUpload);
321 },
322
323 _getFormData: function (options) {
324 var formData;
325 if ($.type(options.formData) === 'function') {
326 return options.formData(options.form);
327 }
328 if ($.isArray(options.formData)) {
329 return options.formData;
330 }
331 if ($.type(options.formData) === 'object') {
332 formData = [];
333 $.each(options.formData, function (name, value) {
334 formData.push({name: name, value: value});
335 });
336 return formData;
337 }
338 return [];
339 },
340
341 _getTotal: function (files) {
342 var total = 0;
343 $.each(files, function (index, file) {
344 total += file.size || 1;
345 });
346 return total;
347 },
348
349 _initProgressObject: function (obj) {
350 var progress = {
351 loaded: 0,
352 total: 0,
353 bitrate: 0
354 };
355 if (obj._progress) {
356 $.extend(obj._progress, progress);
357 } else {
358 obj._progress = progress;
359 }
360 },
361
362 _initResponseObject: function (obj) {
363 var prop;
364 if (obj._response) {
365 for (prop in obj._response) {
366 if (obj._response.hasOwnProperty(prop)) {
367 delete obj._response[prop];
368 }
369 }
370 } else {
371 obj._response = {};
372 }
373 },
374
375 _onProgress: function (e, data) {
376 if (e.lengthComputable) {
377 var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
378 loaded;
379 if (data._time && data.progressInterval &&
380 (now - data._time < data.progressInterval) &&
381 e.loaded !== e.total) {
382 return;
383 }
384 data._time = now;
385 loaded = Math.floor(
386 e.loaded / e.total * (data.chunkSize || data._progress.total)
387 ) + (data.uploadedBytes || 0);
388 // Add the difference from the previously loaded state
389 // to the global loaded counter:
390 this._progress.loaded += (loaded - data._progress.loaded);
391 this._progress.bitrate = this._bitrateTimer.getBitrate(
392 now,
393 this._progress.loaded,
394 data.bitrateInterval
395 );
396 data._progress.loaded = data.loaded = loaded;
397 data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
398 now,
399 loaded,
400 data.bitrateInterval
401 );
402 // Trigger a custom progress event with a total data property set
403 // to the file size(s) of the current upload and a loaded data
404 // property calculated accordingly:
405 this._trigger(
406 'progress',
407 $.Event('progress', {delegatedEvent: e}),
408 data
409 );
410 // Trigger a global progress event for all current file uploads,
411 // including ajax calls queued for sequential file uploads:
412 this._trigger(
413 'progressall',
414 $.Event('progressall', {delegatedEvent: e}),
415 this._progress
416 );
417 }
418 },
419
420 _initProgressListener: function (options) {
421 var that = this,
422 xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
423 // Accesss to the native XHR object is required to add event listeners
424 // for the upload progress event:
425 if (xhr.upload) {
426 $(xhr.upload).bind('progress', function (e) {
427 var oe = e.originalEvent;
428 // Make sure the progress event properties get copied over:
429 e.lengthComputable = oe.lengthComputable;
430 e.loaded = oe.loaded;
431 e.total = oe.total;
432 that._onProgress(e, options);
433 });
434 options.xhr = function () {
435 return xhr;
436 };
437 }
438 },
439
440 _deinitProgressListener: function (options) {
441 var xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
442 if (xhr.upload) {
443 $(xhr.upload).unbind('progress');
444 }
445 },
446
447 _isInstanceOf: function (type, obj) {
448 // Cross-frame instanceof check
449 return Object.prototype.toString.call(obj) === '[object ' + type + ']';
450 },
451
452 _initXHRData: function (options) {
453 var that = this,
454 formData,
455 file = options.files[0],
456 // Ignore non-multipart setting if not supported:
457 multipart = options.multipart || !$.support.xhrFileUpload,
458 paramName = $.type(options.paramName) === 'array' ?
459 options.paramName[0] : options.paramName;
460 options.headers = $.extend({}, options.headers);
461 if (options.contentRange) {
462 options.headers['Content-Range'] = options.contentRange;
463 }
464 if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
465 options.headers['Content-Disposition'] = 'attachment; filename="' +
466 encodeURI(file.uploadName || file.name) + '"';
467 }
468 if (!multipart) {
469 options.contentType = file.type || 'application/octet-stream';
470 options.data = options.blob || file;
471 } else if ($.support.xhrFormDataFileUpload) {
472 if (options.postMessage) {
473 // window.postMessage does not allow sending FormData
474 // objects, so we just add the File/Blob objects to
475 // the formData array and let the postMessage window
476 // create the FormData object out of this array:
477 formData = this._getFormData(options);
478 if (options.blob) {
479 formData.push({
480 name: paramName,
481 value: options.blob
482 });
483 } else {
484 $.each(options.files, function (index, file) {
485 formData.push({
486 name: ($.type(options.paramName) === 'array' &&
487 options.paramName[index]) || paramName,
488 value: file
489 });
490 });
491 }
492 } else {
493 if (that._isInstanceOf('FormData', options.formData)) {
494 formData = options.formData;
495 } else {
496 formData = new FormData();
497 $.each(this._getFormData(options), function (index, field) {
498 formData.append(field.name, field.value);
499 });
500 }
501 if (options.blob) {
502 formData.append(
503 paramName,
504 options.blob,
505 file.uploadName || file.name
506 );
507 } else {
508 $.each(options.files, function (index, file) {
509 // This check allows the tests to run with
510 // dummy objects:
511 if (that._isInstanceOf('File', file) ||
512 that._isInstanceOf('Blob', file)) {
513 formData.append(
514 ($.type(options.paramName) === 'array' &&
515 options.paramName[index]) || paramName,
516 file,
517 file.uploadName || file.name
518 );
519 }
520 });
521 }
522 }
523 options.data = formData;
524 }
525 // Blob reference is not needed anymore, free memory:
526 options.blob = null;
527 },
528
529 _initIframeSettings: function (options) {
530 var targetHost = $('<a></a>').prop('href', options.url).prop('host');
531 // Setting the dataType to iframe enables the iframe transport:
532 options.dataType = 'iframe ' + (options.dataType || '');
533 // The iframe transport accepts a serialized array as form data:
534 options.formData = this._getFormData(options);
535 // Add redirect url to form data on cross-domain uploads:
536 if (options.redirect && targetHost && targetHost !== location.host) {
537 options.formData.push({
538 name: options.redirectParamName || 'redirect',
539 value: options.redirect
540 });
541 }
542 },
543
544 _initDataSettings: function (options) {
545 if (this._isXHRUpload(options)) {
546 if (!this._chunkedUpload(options, true)) {
547 if (!options.data) {
548 this._initXHRData(options);
549 }
550 this._initProgressListener(options);
551 }
552 if (options.postMessage) {
553 // Setting the dataType to postmessage enables the
554 // postMessage transport:
555 options.dataType = 'postmessage ' + (options.dataType || '');
556 }
557 } else {
558 this._initIframeSettings(options);
559 }
560 },
561
562 _getParamName: function (options) {
563 var fileInput = $(options.fileInput),
564 paramName = options.paramName;
565 if (!paramName) {
566 paramName = [];
567 fileInput.each(function () {
568 var input = $(this),
569 name = input.prop('name') || 'files[]',
570 i = (input.prop('files') || [1]).length;
571 while (i) {
572 paramName.push(name);
573 i -= 1;
574 }
575 });
576 if (!paramName.length) {
577 paramName = [fileInput.prop('name') || 'files[]'];
578 }
579 } else if (!$.isArray(paramName)) {
580 paramName = [paramName];
581 }
582 return paramName;
583 },
584
585 _initFormSettings: function (options) {
586 // Retrieve missing options from the input field and the
587 // associated form, if available:
588 if (!options.form || !options.form.length) {
589 options.form = $(options.fileInput.prop('form'));
590 // If the given file input doesn't have an associated form,
591 // use the default widget file input's form:
592 if (!options.form.length) {
593 options.form = $(this.options.fileInput.prop('form'));
594 }
595 }
596 options.paramName = this._getParamName(options);
597 if (!options.url) {
598 options.url = options.form.prop('action') || location.href;
599 }
600 // The HTTP request method must be "POST" or "PUT":
601 options.type = (options.type ||
602 ($.type(options.form.prop('method')) === 'string' &&
603 options.form.prop('method')) || ''
604 ).toUpperCase();
605 if (options.type !== 'POST' && options.type !== 'PUT' &&
606 options.type !== 'PATCH') {
607 options.type = 'POST';
608 }
609 if (!options.formAcceptCharset) {
610 options.formAcceptCharset = options.form.attr('accept-charset');
611 }
612 },
613
614 _getAJAXSettings: function (data) {
615 var options = $.extend({}, this.options, data);
616 this._initFormSettings(options);
617 this._initDataSettings(options);
618 return options;
619 },
620
621 // jQuery 1.6 doesn't provide .state(),
622 // while jQuery 1.8+ removed .isRejected() and .isResolved():
623 _getDeferredState: function (deferred) {
624 if (deferred.state) {
625 return deferred.state();
626 }
627 if (deferred.isResolved()) {
628 return 'resolved';
629 }
630 if (deferred.isRejected()) {
631 return 'rejected';
632 }
633 return 'pending';
634 },
635
636 // Maps jqXHR callbacks to the equivalent
637 // methods of the given Promise object:
638 _enhancePromise: function (promise) {
639 promise.success = promise.done;
640 promise.error = promise.fail;
641 promise.complete = promise.always;
642 return promise;
643 },
644
645 // Creates and returns a Promise object enhanced with
646 // the jqXHR methods abort, success, error and complete:
647 _getXHRPromise: function (resolveOrReject, context, args) {
648 var dfd = $.Deferred(),
649 promise = dfd.promise();
650 context = context || this.options.context || promise;
651 if (resolveOrReject === true) {
652 dfd.resolveWith(context, args);
653 } else if (resolveOrReject === false) {
654 dfd.rejectWith(context, args);
655 }
656 promise.abort = dfd.promise;
657 return this._enhancePromise(promise);
658 },
659
660 // Adds convenience methods to the data callback argument:
661 _addConvenienceMethods: function (e, data) {
662 var that = this,
663 getPromise = function (args) {
664 return $.Deferred().resolveWith(that, args).promise();
665 };
666 data.process = function (resolveFunc, rejectFunc) {
667 if (resolveFunc || rejectFunc) {
668 data._processQueue = this._processQueue =
669 (this._processQueue || getPromise([this])).then(
670 function () {
671 if (data.errorThrown) {
672 return $.Deferred()
673 .rejectWith(that, [data]).promise();
674 }
675 return getPromise(arguments);
676 }
677 ).then(resolveFunc, rejectFunc);
678 }
679 return this._processQueue || getPromise([this]);
680 };
681 data.submit = function () {
682 if (this.state() !== 'pending') {
683 data.jqXHR = this.jqXHR =
684 (that._trigger(
685 'submit',
686 $.Event('submit', {delegatedEvent: e}),
687 this
688 ) !== false) && that._onSend(e, this);
689 }
690 return this.jqXHR || that._getXHRPromise();
691 };
692 data.abort = function () {
693 if (this.jqXHR) {
694 return this.jqXHR.abort();
695 }
696 this.errorThrown = 'abort';
697 that._trigger('fail', null, this);
698 return that._getXHRPromise(false);
699 };
700 data.state = function () {
701 if (this.jqXHR) {
702 return that._getDeferredState(this.jqXHR);
703 }
704 if (this._processQueue) {
705 return that._getDeferredState(this._processQueue);
706 }
707 };
708 data.processing = function () {
709 return !this.jqXHR && this._processQueue && that
710 ._getDeferredState(this._processQueue) === 'pending';
711 };
712 data.progress = function () {
713 return this._progress;
714 };
715 data.response = function () {
716 return this._response;
717 };
718 },
719
720 // Parses the Range header from the server response
721 // and returns the uploaded bytes:
722 _getUploadedBytes: function (jqXHR) {
723 var range = jqXHR.getResponseHeader('Range'),
724 parts = range && range.split('-'),
725 upperBytesPos = parts && parts.length > 1 &&
726 parseInt(parts[1], 10);
727 return upperBytesPos && upperBytesPos + 1;
728 },
729
730 // Uploads a file in multiple, sequential requests
731 // by splitting the file up in multiple blob chunks.
732 // If the second parameter is true, only tests if the file
733 // should be uploaded in chunks, but does not invoke any
734 // upload requests:
735 _chunkedUpload: function (options, testOnly) {
736 options.uploadedBytes = options.uploadedBytes || 0;
737 var that = this,
738 file = options.files[0],
739 fs = file.size,
740 ub = options.uploadedBytes,
741 mcs = options.maxChunkSize || fs,
742 slice = this._blobSlice,
743 dfd = $.Deferred(),
744 promise = dfd.promise(),
745 jqXHR,
746 upload;
747 if (!(this._isXHRUpload(options) && slice && (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)) ||
748 options.data) {
749 return false;
750 }
751 if (testOnly) {
752 return true;
753 }
754 if (ub >= fs) {
755 file.error = options.i18n('uploadedBytes');
756 return this._getXHRPromise(
757 false,
758 options.context,
759 [null, 'error', file.error]
760 );
761 }
762 // The chunk upload method:
763 upload = function () {
764 // Clone the options object for each chunk upload:
765 var o = $.extend({}, options),
766 currentLoaded = o._progress.loaded;
767 o.blob = slice.call(
768 file,
769 ub,
770 ub + ($.type(mcs) === 'function' ? mcs(o) : mcs),
771 file.type
772 );
773 // Store the current chunk size, as the blob itself
774 // will be dereferenced after data processing:
775 o.chunkSize = o.blob.size;
776 // Expose the chunk bytes position range:
777 o.contentRange = 'bytes ' + ub + '-' +
778 (ub + o.chunkSize - 1) + '/' + fs;
779 // Trigger chunkbeforesend to allow form data to be updated for this chunk
780 that._trigger('chunkbeforesend', null, o);
781 // Process the upload data (the blob and potential form data):
782 that._initXHRData(o);
783 // Add progress listeners for this chunk upload:
784 that._initProgressListener(o);
785 jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
786 that._getXHRPromise(false, o.context))
787 .done(function (result, textStatus, jqXHR) {
788 ub = that._getUploadedBytes(jqXHR) ||
789 (ub + o.chunkSize);
790 // Create a progress event if no final progress event
791 // with loaded equaling total has been triggered
792 // for this chunk:
793 if (currentLoaded + o.chunkSize - o._progress.loaded) {
794 that._onProgress($.Event('progress', {
795 lengthComputable: true,
796 loaded: ub - o.uploadedBytes,
797 total: ub - o.uploadedBytes
798 }), o);
799 }
800 options.uploadedBytes = o.uploadedBytes = ub;
801 o.result = result;
802 o.textStatus = textStatus;
803 o.jqXHR = jqXHR;
804 that._trigger('chunkdone', null, o);
805 that._trigger('chunkalways', null, o);
806 if (ub < fs) {
807 // File upload not yet complete,
808 // continue with the next chunk:
809 upload();
810 } else {
811 dfd.resolveWith(
812 o.context,
813 [result, textStatus, jqXHR]
814 );
815 }
816 })
817 .fail(function (jqXHR, textStatus, errorThrown) {
818 o.jqXHR = jqXHR;
819 o.textStatus = textStatus;
820 o.errorThrown = errorThrown;
821 that._trigger('chunkfail', null, o);
822 that._trigger('chunkalways', null, o);
823 dfd.rejectWith(
824 o.context,
825 [jqXHR, textStatus, errorThrown]
826 );
827 })
828 .always(function () {
829 that._deinitProgressListener(o);
830 });
831 };
832 this._enhancePromise(promise);
833 promise.abort = function () {
834 return jqXHR.abort();
835 };
836 upload();
837 return promise;
838 },
839
840 _beforeSend: function (e, data) {
841 if (this._active === 0) {
842 // the start callback is triggered when an upload starts
843 // and no other uploads are currently running,
844 // equivalent to the global ajaxStart event:
845 this._trigger('start');
846 // Set timer for global bitrate progress calculation:
847 this._bitrateTimer = new this._BitrateTimer();
848 // Reset the global progress values:
849 this._progress.loaded = this._progress.total = 0;
850 this._progress.bitrate = 0;
851 }
852 // Make sure the container objects for the .response() and
853 // .progress() methods on the data object are available
854 // and reset to their initial state:
855 this._initResponseObject(data);
856 this._initProgressObject(data);
857 data._progress.loaded = data.loaded = data.uploadedBytes || 0;
858 data._progress.total = data.total = this._getTotal(data.files) || 1;
859 data._progress.bitrate = data.bitrate = 0;
860 this._active += 1;
861 // Initialize the global progress values:
862 this._progress.loaded += data.loaded;
863 this._progress.total += data.total;
864 },
865
866 _onDone: function (result, textStatus, jqXHR, options) {
867 var total = options._progress.total,
868 response = options._response;
869 if (options._progress.loaded < total) {
870 // Create a progress event if no final progress event
871 // with loaded equaling total has been triggered:
872 this._onProgress($.Event('progress', {
873 lengthComputable: true,
874 loaded: total,
875 total: total
876 }), options);
877 }
878 response.result = options.result = result;
879 response.textStatus = options.textStatus = textStatus;
880 response.jqXHR = options.jqXHR = jqXHR;
881 this._trigger('done', null, options);
882 },
883
884 _onFail: function (jqXHR, textStatus, errorThrown, options) {
885 var response = options._response;
886 if (options.recalculateProgress) {
887 // Remove the failed (error or abort) file upload from
888 // the global progress calculation:
889 this._progress.loaded -= options._progress.loaded;
890 this._progress.total -= options._progress.total;
891 }
892 response.jqXHR = options.jqXHR = jqXHR;
893 response.textStatus = options.textStatus = textStatus;
894 response.errorThrown = options.errorThrown = errorThrown;
895 this._trigger('fail', null, options);
896 },
897
898 _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
899 // jqXHRorResult, textStatus and jqXHRorError are added to the
900 // options object via done and fail callbacks
901 this._trigger('always', null, options);
902 },
903
904 _onSend: function (e, data) {
905 if (!data.submit) {
906 this._addConvenienceMethods(e, data);
907 }
908 var that = this,
909 jqXHR,
910 aborted,
911 slot,
912 pipe,
913 options = that._getAJAXSettings(data),
914 send = function () {
915 that._sending += 1;
916 // Set timer for bitrate progress calculation:
917 options._bitrateTimer = new that._BitrateTimer();
918 jqXHR = jqXHR || (
919 ((aborted || that._trigger(
920 'send',
921 $.Event('send', {delegatedEvent: e}),
922 options
923 ) === false) &&
924 that._getXHRPromise(false, options.context, aborted)) ||
925 that._chunkedUpload(options) || $.ajax(options)
926 ).done(function (result, textStatus, jqXHR) {
927 that._onDone(result, textStatus, jqXHR, options);
928 }).fail(function (jqXHR, textStatus, errorThrown) {
929 that._onFail(jqXHR, textStatus, errorThrown, options);
930 }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
931 that._deinitProgressListener(options);
932 that._onAlways(
933 jqXHRorResult,
934 textStatus,
935 jqXHRorError,
936 options
937 );
938 that._sending -= 1;
939 that._active -= 1;
940 if (options.limitConcurrentUploads &&
941 options.limitConcurrentUploads > that._sending) {
942 // Start the next queued upload,
943 // that has not been aborted:
944 var nextSlot = that._slots.shift();
945 while (nextSlot) {
946 if (that._getDeferredState(nextSlot) === 'pending') {
947 nextSlot.resolve();
948 break;
949 }
950 nextSlot = that._slots.shift();
951 }
952 }
953 if (that._active === 0) {
954 // The stop callback is triggered when all uploads have
955 // been completed, equivalent to the global ajaxStop event:
956 that._trigger('stop');
957 }
958 });
959 return jqXHR;
960 };
961 this._beforeSend(e, options);
962 if (this.options.sequentialUploads ||
963 (this.options.limitConcurrentUploads &&
964 this.options.limitConcurrentUploads <= this._sending)) {
965 if (this.options.limitConcurrentUploads > 1) {
966 slot = $.Deferred();
967 this._slots.push(slot);
968 pipe = slot.then(send);
969 } else {
970 this._sequence = this._sequence.then(send, send);
971 pipe = this._sequence;
972 }
973 // Return the piped Promise object, enhanced with an abort method,
974 // which is delegated to the jqXHR object of the current upload,
975 // and jqXHR callbacks mapped to the equivalent Promise methods:
976 pipe.abort = function () {
977 aborted = [undefined, 'abort', 'abort'];
978 if (!jqXHR) {
979 if (slot) {
980 slot.rejectWith(options.context, aborted);
981 }
982 return send();
983 }
984 return jqXHR.abort();
985 };
986 return this._enhancePromise(pipe);
987 }
988 return send();
989 },
990
991 _onAdd: function (e, data) {
992 var that = this,
993 result = true,
994 options = $.extend({}, this.options, data),
995 files = data.files,
996 filesLength = files.length,
997 limit = options.limitMultiFileUploads,
998 limitSize = options.limitMultiFileUploadSize,
999 overhead = options.limitMultiFileUploadSizeOverhead,
1000 batchSize = 0,
1001 paramName = this._getParamName(options),
1002 paramNameSet,
1003 paramNameSlice,
1004 fileSet,
1005 i,
1006 j = 0;
1007 if (!filesLength) {
1008 return false;
1009 }
1010 if (limitSize && files[0].size === undefined) {
1011 limitSize = undefined;
1012 }
1013 if (!(options.singleFileUploads || limit || limitSize) ||
1014 !this._isXHRUpload(options)) {
1015 fileSet = [files];
1016 paramNameSet = [paramName];
1017 } else if (!(options.singleFileUploads || limitSize) && limit) {
1018 fileSet = [];
1019 paramNameSet = [];
1020 for (i = 0; i < filesLength; i += limit) {
1021 fileSet.push(files.slice(i, i + limit));
1022 paramNameSlice = paramName.slice(i, i + limit);
1023 if (!paramNameSlice.length) {
1024 paramNameSlice = paramName;
1025 }
1026 paramNameSet.push(paramNameSlice);
1027 }
1028 } else if (!options.singleFileUploads && limitSize) {
1029 fileSet = [];
1030 paramNameSet = [];
1031 for (i = 0; i < filesLength; i = i + 1) {
1032 batchSize += files[i].size + overhead;
1033 if (i + 1 === filesLength ||
1034 ((batchSize + files[i + 1].size + overhead) > limitSize) ||
1035 (limit && i + 1 - j >= limit)) {
1036 fileSet.push(files.slice(j, i + 1));
1037 paramNameSlice = paramName.slice(j, i + 1);
1038 if (!paramNameSlice.length) {
1039 paramNameSlice = paramName;
1040 }
1041 paramNameSet.push(paramNameSlice);
1042 j = i + 1;
1043 batchSize = 0;
1044 }
1045 }
1046 } else {
1047 paramNameSet = paramName;
1048 }
1049 data.originalFiles = files;
1050 $.each(fileSet || files, function (index, element) {
1051 var newData = $.extend({}, data);
1052 newData.files = fileSet ? element : [element];
1053 newData.paramName = paramNameSet[index];
1054 that._initResponseObject(newData);
1055 that._initProgressObject(newData);
1056 that._addConvenienceMethods(e, newData);
1057 result = that._trigger(
1058 'add',
1059 $.Event('add', {delegatedEvent: e}),
1060 newData
1061 );
1062 return result;
1063 });
1064 return result;
1065 },
1066
1067 _replaceFileInput: function (data) {
1068 var input = data.fileInput,
1069 inputClone = input.clone(true),
1070 restoreFocus = input.is(document.activeElement);
1071 // Add a reference for the new cloned file input to the data argument:
1072 data.fileInputClone = inputClone;
1073 $('<form></form>').append(inputClone)[0].reset();
1074 // Detaching allows to insert the fileInput on another form
1075 // without loosing the file input value:
1076 input.after(inputClone).detach();
1077 // If the fileInput had focus before it was detached,
1078 // restore focus to the inputClone.
1079 if (restoreFocus) {
1080 inputClone.focus();
1081 }
1082 // Avoid memory leaks with the detached file input:
1083 $.cleanData(input.unbind('remove'));
1084 // Replace the original file input element in the fileInput
1085 // elements set with the clone, which has been copied including
1086 // event handlers:
1087 this.options.fileInput = this.options.fileInput.map(function (i, el) {
1088 if (el === input[0]) {
1089 return inputClone[0];
1090 }
1091 return el;
1092 });
1093 // If the widget has been initialized on the file input itself,
1094 // override this.element with the file input clone:
1095 if (input[0] === this.element[0]) {
1096 this.element = inputClone;
1097 }
1098 },
1099
1100 _handleFileTreeEntry: function (entry, path) {
1101 var that = this,
1102 dfd = $.Deferred(),
1103 entries = [],
1104 dirReader,
1105 errorHandler = function (e) {
1106 if (e && !e.entry) {
1107 e.entry = entry;
1108 }
1109 // Since $.when returns immediately if one
1110 // Deferred is rejected, we use resolve instead.
1111 // This allows valid files and invalid items
1112 // to be returned together in one set:
1113 dfd.resolve([e]);
1114 },
1115 successHandler = function (entries) {
1116 that._handleFileTreeEntries(
1117 entries,
1118 path + entry.name + '/'
1119 ).done(function (files) {
1120 dfd.resolve(files);
1121 }).fail(errorHandler);
1122 },
1123 readEntries = function () {
1124 dirReader.readEntries(function (results) {
1125 if (!results.length) {
1126 successHandler(entries);
1127 } else {
1128 entries = entries.concat(results);
1129 readEntries();
1130 }
1131 }, errorHandler);
1132 };
1133 path = path || '';
1134 if (entry.isFile) {
1135 if (entry._file) {
1136 // Workaround for Chrome bug #149735
1137 entry._file.relativePath = path;
1138 dfd.resolve(entry._file);
1139 } else {
1140 entry.file(function (file) {
1141 file.relativePath = path;
1142 dfd.resolve(file);
1143 }, errorHandler);
1144 }
1145 } else if (entry.isDirectory) {
1146 dirReader = entry.createReader();
1147 readEntries();
1148 } else {
1149 // Return an empty list for file system items
1150 // other than files or directories:
1151 dfd.resolve([]);
1152 }
1153 return dfd.promise();
1154 },
1155
1156 _handleFileTreeEntries: function (entries, path) {
1157 var that = this;
1158 return $.when.apply(
1159 $,
1160 $.map(entries, function (entry) {
1161 return that._handleFileTreeEntry(entry, path);
1162 })
1163 ).then(function () {
1164 return Array.prototype.concat.apply(
1165 [],
1166 arguments
1167 );
1168 });
1169 },
1170
1171 _getDroppedFiles: function (dataTransfer) {
1172 dataTransfer = dataTransfer || {};
1173 var items = dataTransfer.items;
1174 if (items && items.length && (items[0].webkitGetAsEntry ||
1175 items[0].getAsEntry)) {
1176 return this._handleFileTreeEntries(
1177 $.map(items, function (item) {
1178 var entry;
1179 if (item.webkitGetAsEntry) {
1180 entry = item.webkitGetAsEntry();
1181 if (entry) {
1182 // Workaround for Chrome bug #149735:
1183 entry._file = item.getAsFile();
1184 }
1185 return entry;
1186 }
1187 return item.getAsEntry();
1188 })
1189 );
1190 }
1191 return $.Deferred().resolve(
1192 $.makeArray(dataTransfer.files)
1193 ).promise();
1194 },
1195
1196 _getSingleFileInputFiles: function (fileInput) {
1197 fileInput = $(fileInput);
1198 var entries = fileInput.prop('webkitEntries') ||
1199 fileInput.prop('entries'),
1200 files,
1201 value;
1202 if (entries && entries.length) {
1203 return this._handleFileTreeEntries(entries);
1204 }
1205 files = $.makeArray(fileInput.prop('files'));
1206 if (!files.length) {
1207 value = fileInput.prop('value');
1208 if (!value) {
1209 return $.Deferred().resolve([]).promise();
1210 }
1211 // If the files property is not available, the browser does not
1212 // support the File API and we add a pseudo File object with
1213 // the input value as name with path information removed:
1214 files = [{name: value.replace(/^.*\\/, '')}];
1215 } else if (files[0].name === undefined && files[0].fileName) {
1216 // File normalization for Safari 4 and Firefox 3:
1217 $.each(files, function (index, file) {
1218 file.name = file.fileName;
1219 file.size = file.fileSize;
1220 });
1221 }
1222 return $.Deferred().resolve(files).promise();
1223 },
1224
1225 _getFileInputFiles: function (fileInput) {
1226 if (!(fileInput instanceof $) || fileInput.length === 1) {
1227 return this._getSingleFileInputFiles(fileInput);
1228 }
1229 return $.when.apply(
1230 $,
1231 $.map(fileInput, this._getSingleFileInputFiles)
1232 ).then(function () {
1233 return Array.prototype.concat.apply(
1234 [],
1235 arguments
1236 );
1237 });
1238 },
1239
1240 _onChange: function (e) {
1241 var that = this,
1242 data = {
1243 fileInput: $(e.target),
1244 form: $(e.target.form)
1245 };
1246 this._getFileInputFiles(data.fileInput).always(function (files) {
1247 data.files = files;
1248 if (that.options.replaceFileInput) {
1249 that._replaceFileInput(data);
1250 }
1251 if (that._trigger(
1252 'change',
1253 $.Event('change', {delegatedEvent: e}),
1254 data
1255 ) !== false) {
1256 that._onAdd(e, data);
1257 }
1258 });
1259 },
1260
1261 _onPaste: function (e) {
1262 var items = e.originalEvent && e.originalEvent.clipboardData &&
1263 e.originalEvent.clipboardData.items,
1264 data = {files: []};
1265 if (items && items.length) {
1266 $.each(items, function (index, item) {
1267 var file = item.getAsFile && item.getAsFile();
1268 if (file) {
1269 data.files.push(file);
1270 }
1271 });
1272 if (this._trigger(
1273 'paste',
1274 $.Event('paste', {delegatedEvent: e}),
1275 data
1276 ) !== false) {
1277 this._onAdd(e, data);
1278 }
1279 }
1280 },
1281
1282 _onDrop: function (e) {
1283 e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1284 var that = this,
1285 dataTransfer = e.dataTransfer,
1286 data = {};
1287 if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1288 e.preventDefault();
1289 this._getDroppedFiles(dataTransfer).always(function (files) {
1290 data.files = files;
1291 if (that._trigger(
1292 'drop',
1293 $.Event('drop', {delegatedEvent: e}),
1294 data
1295 ) !== false) {
1296 that._onAdd(e, data);
1297 }
1298 });
1299 }
1300 },
1301
1302 _onDragOver: getDragHandler('dragover'),
1303
1304 _onDragEnter: getDragHandler('dragenter'),
1305
1306 _onDragLeave: getDragHandler('dragleave'),
1307
1308 _initEventHandlers: function () {
1309 if (this._isXHRUpload(this.options)) {
1310 this._on(this.options.dropZone, {
1311 dragover: this._onDragOver,
1312 drop: this._onDrop,
1313 // event.preventDefault() on dragenter is required for IE10+:
1314 dragenter: this._onDragEnter,
1315 // dragleave is not required, but added for completeness:
1316 dragleave: this._onDragLeave
1317 });
1318 this._on(this.options.pasteZone, {
1319 paste: this._onPaste
1320 });
1321 }
1322 if ($.support.fileInput) {
1323 this._on(this.options.fileInput, {
1324 change: this._onChange
1325 });
1326 }
1327 },
1328
1329 _destroyEventHandlers: function () {
1330 this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
1331 this._off(this.options.pasteZone, 'paste');
1332 this._off(this.options.fileInput, 'change');
1333 },
1334
1335 _destroy: function () {
1336 this._destroyEventHandlers();
1337 },
1338
1339 _setOption: function (key, value) {
1340 var reinit = $.inArray(key, this._specialOptions) !== -1;
1341 if (reinit) {
1342 this._destroyEventHandlers();
1343 }
1344 this._super(key, value);
1345 if (reinit) {
1346 this._initSpecialOptions();
1347 this._initEventHandlers();
1348 }
1349 },
1350
1351 _initSpecialOptions: function () {
1352 var options = this.options;
1353 if (options.fileInput === undefined) {
1354 options.fileInput = this.element.is('input[type="file"]') ?
1355 this.element : this.element.find('input[type="file"]');
1356 } else if (!(options.fileInput instanceof $)) {
1357 options.fileInput = $(options.fileInput);
1358 }
1359 if (!(options.dropZone instanceof $)) {
1360 options.dropZone = $(options.dropZone);
1361 }
1362 if (!(options.pasteZone instanceof $)) {
1363 options.pasteZone = $(options.pasteZone);
1364 }
1365 },
1366
1367 _getRegExp: function (str) {
1368 var parts = str.split('/'),
1369 modifiers = parts.pop();
1370 parts.shift();
1371 return new RegExp(parts.join('/'), modifiers);
1372 },
1373
1374 _isRegExpOption: function (key, value) {
1375 return key !== 'url' && $.type(value) === 'string' &&
1376 /^\/.*\/[igm]{0,3}$/.test(value);
1377 },
1378
1379 _initDataAttributes: function () {
1380 var that = this,
1381 options = this.options,
1382 data = this.element.data();
1383 // Initialize options set via HTML5 data-attributes:
1384 $.each(
1385 this.element[0].attributes,
1386 function (index, attr) {
1387 var key = attr.name.toLowerCase(),
1388 value;
1389 if (/^data-/.test(key)) {
1390 // Convert hyphen-ated key to camelCase:
1391 key = key.slice(5).replace(/-[a-z]/g, function (str) {
1392 return str.charAt(1).toUpperCase();
1393 });
1394 value = data[key];
1395 if (that._isRegExpOption(key, value)) {
1396 value = that._getRegExp(value);
1397 }
1398 options[key] = value;
1399 }
1400 }
1401 );
1402 },
1403
1404 _create: function () {
1405 this._initDataAttributes();
1406 this._initSpecialOptions();
1407 this._slots = [];
1408 this._sequence = this._getXHRPromise(true);
1409 this._sending = this._active = 0;
1410 this._initProgressObject(this);
1411 this._initEventHandlers();
1412 },
1413
1414 // This method is exposed to the widget API and allows to query
1415 // the number of active uploads:
1416 active: function () {
1417 return this._active;
1418 },
1419
1420 // This method is exposed to the widget API and allows to query
1421 // the widget upload progress.
1422 // It returns an object with loaded, total and bitrate properties
1423 // for the running uploads:
1424 progress: function () {
1425 return this._progress;
1426 },
1427
1428 // This method is exposed to the widget API and allows adding files
1429 // using the fileupload API. The data parameter accepts an object which
1430 // must have a files property and can contain additional options:
1431 // .fileupload('add', {files: filesList});
1432 add: function (data) {
1433 var that = this;
1434 if (!data || this.options.disabled) {
1435 return;
1436 }
1437 if (data.fileInput && !data.files) {
1438 this._getFileInputFiles(data.fileInput).always(function (files) {
1439 data.files = files;
1440 that._onAdd(null, data);
1441 });
1442 } else {
1443 data.files = $.makeArray(data.files);
1444 this._onAdd(null, data);
1445 }
1446 },
1447
1448 // This method is exposed to the widget API and allows sending files
1449 // using the fileupload API. The data parameter accepts an object which
1450 // must have a files or fileInput property and can contain additional options:
1451 // .fileupload('send', {files: filesList});
1452 // The method returns a Promise object for the file upload call.
1453 send: function (data) {
1454 if (data && !this.options.disabled) {
1455 if (data.fileInput && !data.files) {
1456 var that = this,
1457 dfd = $.Deferred(),
1458 promise = dfd.promise(),
1459 jqXHR,
1460 aborted;
1461 promise.abort = function () {
1462 aborted = true;
1463 if (jqXHR) {
1464 return jqXHR.abort();
1465 }
1466 dfd.reject(null, 'abort', 'abort');
1467 return promise;
1468 };
1469 this._getFileInputFiles(data.fileInput).always(
1470 function (files) {
1471 if (aborted) {
1472 return;
1473 }
1474 if (!files.length) {
1475 dfd.reject();
1476 return;
1477 }
1478 data.files = files;
1479 jqXHR = that._onSend(null, data);
1480 jqXHR.then(
1481 function (result, textStatus, jqXHR) {
1482 dfd.resolve(result, textStatus, jqXHR);
1483 },
1484 function (jqXHR, textStatus, errorThrown) {
1485 dfd.reject(jqXHR, textStatus, errorThrown);
1486 }
1487 );
1488 }
1489 );
1490 return this._enhancePromise(promise);
1491 }
1492 data.files = $.makeArray(data.files);
1493 if (data.files.length) {
1494 return this._onSend(null, data);
1495 }
1496 }
1497 return this._getXHRPromise(false, data && data.context);
1498 }
1499
1500 });
1501
1502 }));
1503