PluginProbe
WpStream – Live Streaming, Video on Demand, Pay Per View / trunk
WpStream – Live Streaming, Video on Demand, Pay Per View vtrunk
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 trunk, at admin/js/jquery.fileupload.js

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