resources/lib/blueimp-file-upload/js/jquery.fileupload.js   F
last analyzed

Complexity

Total Complexity 281
Complexity/F 2.58

Size

Lines of Code 1472
Function Count 109

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 0
dl 0
loc 1472
rs 2.4
c 0
b 0
f 0
wmc 281
mnd 6
bc 244
fnc 109
bpm 2.2385
cpm 2.5779
noi 3

1 Function

Rating   Name   Duplication   Size   Complexity  
B jquery.fileupload.js ➔ ?!? 0 1454 1

How to fix   Complexity   

Complexity

Complex classes like resources/lib/blueimp-file-upload/js/jquery.fileupload.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 &&
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if data.autoUpload || data...."option", "autoUpload") is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
216
                        $(this).fileupload('option', 'autoUpload'))) {
217
                    data.process().done(function () {
218
                        data.submit();
219
                    });
220
                }
221
            },
222
223
            // Other callbacks:
224
225
            // Callback for the submit event of each file upload:
226
            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
227
228
            // Callback for the start of each file upload request:
229
            // send: function (e, data) {}, // .bind('fileuploadsend', func);
230
231
            // Callback for successful uploads:
232
            // done: function (e, data) {}, // .bind('fileuploaddone', func);
233
234
            // Callback for failed (abort or error) uploads:
235
            // fail: function (e, data) {}, // .bind('fileuploadfail', func);
236
237
            // Callback for completed (success, abort or error) requests:
238
            // always: function (e, data) {}, // .bind('fileuploadalways', func);
239
240
            // Callback for upload progress events:
241
            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
242
243
            // Callback for global upload progress events:
244
            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
245
246
            // Callback for uploads start, equivalent to the global ajaxStart event:
247
            // start: function (e) {}, // .bind('fileuploadstart', func);
248
249
            // Callback for uploads stop, equivalent to the global ajaxStop event:
250
            // stop: function (e) {}, // .bind('fileuploadstop', func);
251
252
            // Callback for change events of the fileInput(s):
253
            // change: function (e, data) {}, // .bind('fileuploadchange', func);
254
255
            // Callback for paste events to the pasteZone(s):
256
            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
257
258
            // Callback for drop events of the dropZone(s):
259
            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
260
261
            // Callback for dragover events of the dropZone(s):
262
            // dragover: function (e) {}, // .bind('fileuploaddragover', func);
263
264
            // Callback for the start of each chunk upload request:
265
            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
266
267
            // Callback for successful chunk uploads:
268
            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
269
270
            // Callback for failed (abort or error) chunk uploads:
271
            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
272
273
            // Callback for completed (success, abort or error) chunk upload requests:
274
            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
275
276
            // The plugin options are used as settings object for the ajax calls.
277
            // The following are jQuery ajax settings required for the file uploads:
278
            processData: false,
279
            contentType: false,
280
            cache: false,
281
            timeout: 0
282
        },
283
284
        // A list of options that require reinitializing event listeners and/or
285
        // special initialization code:
286
        _specialOptions: [
287
            'fileInput',
288
            'dropZone',
289
            'pasteZone',
290
            'multipart',
291
            'forceIframeTransport'
292
        ],
293
294
        _blobSlice: $.support.blobSlice && function () {
295
            var slice = this.slice || this.webkitSlice || this.mozSlice;
296
            return slice.apply(this, arguments);
297
        },
298
299
        _BitrateTimer: function () {
300
            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
301
            this.loaded = 0;
302
            this.bitrate = 0;
303
            this.getBitrate = function (now, loaded, interval) {
304
                var timeDiff = now - this.timestamp;
305
                if (!this.bitrate || !interval || timeDiff > interval) {
306
                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
307
                    this.loaded = loaded;
308
                    this.timestamp = now;
309
                }
310
                return this.bitrate;
311
            };
312
        },
313
314
        _isXHRUpload: function (options) {
315
            return !options.forceIframeTransport &&
316
                ((!options.multipart && $.support.xhrFileUpload) ||
317
                $.support.xhrFormDataFileUpload);
318
        },
319
320
        _getFormData: function (options) {
321
            var formData;
322
            if ($.type(options.formData) === 'function') {
323
                return options.formData(options.form);
324
            }
325
            if ($.isArray(options.formData)) {
326
                return options.formData;
327
            }
328
            if ($.type(options.formData) === 'object') {
329
                formData = [];
330
                $.each(options.formData, function (name, value) {
331
                    formData.push({name: name, value: value});
332
                });
333
                return formData;
334
            }
335
            return [];
336
        },
337
338
        _getTotal: function (files) {
339
            var total = 0;
340
            $.each(files, function (index, file) {
341
                total += file.size || 1;
342
            });
343
            return total;
344
        },
345
346
        _initProgressObject: function (obj) {
347
            var progress = {
348
                loaded: 0,
349
                total: 0,
350
                bitrate: 0
351
            };
352
            if (obj._progress) {
353
                $.extend(obj._progress, progress);
354
            } else {
355
                obj._progress = progress;
356
            }
357
        },
358
359
        _initResponseObject: function (obj) {
360
            var prop;
361
            if (obj._response) {
362
                for (prop in obj._response) {
363
                    if (obj._response.hasOwnProperty(prop)) {
364
                        delete obj._response[prop];
365
                    }
366
                }
367
            } else {
368
                obj._response = {};
369
            }
370
        },
371
372
        _onProgress: function (e, data) {
373
            if (e.lengthComputable) {
374
                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
375
                    loaded;
376
                if (data._time && data.progressInterval &&
377
                        (now - data._time < data.progressInterval) &&
378
                        e.loaded !== e.total) {
379
                    return;
380
                }
381
                data._time = now;
382
                loaded = Math.floor(
383
                    e.loaded / e.total * (data.chunkSize || data._progress.total)
384
                ) + (data.uploadedBytes || 0);
385
                // Add the difference from the previously loaded state
386
                // to the global loaded counter:
387
                this._progress.loaded += (loaded - data._progress.loaded);
388
                this._progress.bitrate = this._bitrateTimer.getBitrate(
389
                    now,
390
                    this._progress.loaded,
391
                    data.bitrateInterval
392
                );
393
                data._progress.loaded = data.loaded = loaded;
394
                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
395
                    now,
396
                    loaded,
397
                    data.bitrateInterval
398
                );
399
                // Trigger a custom progress event with a total data property set
400
                // to the file size(s) of the current upload and a loaded data
401
                // property calculated accordingly:
402
                this._trigger(
403
                    'progress',
404
                    $.Event('progress', {delegatedEvent: e}),
405
                    data
406
                );
407
                // Trigger a global progress event for all current file uploads,
408
                // including ajax calls queued for sequential file uploads:
409
                this._trigger(
410
                    'progressall',
411
                    $.Event('progressall', {delegatedEvent: e}),
412
                    this._progress
413
                );
414
            }
415
        },
416
417
        _initProgressListener: function (options) {
418
            var that = this,
419
                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
420
            // Accesss to the native XHR object is required to add event listeners
421
            // for the upload progress event:
422
            if (xhr.upload) {
423
                $(xhr.upload).bind('progress', function (e) {
424
                    var oe = e.originalEvent;
425
                    // Make sure the progress event properties get copied over:
426
                    e.lengthComputable = oe.lengthComputable;
427
                    e.loaded = oe.loaded;
428
                    e.total = oe.total;
429
                    that._onProgress(e, options);
430
                });
431
                options.xhr = function () {
432
                    return xhr;
433
                };
434
            }
435
        },
436
437
        _isInstanceOf: function (type, obj) {
438
            // Cross-frame instanceof check
439
            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
440
        },
441
442
        _initXHRData: function (options) {
443
            var that = this,
444
                formData,
445
                file = options.files[0],
446
                // Ignore non-multipart setting if not supported:
447
                multipart = options.multipart || !$.support.xhrFileUpload,
448
                paramName = $.type(options.paramName) === 'array' ?
449
                    options.paramName[0] : options.paramName;
450
            options.headers = $.extend({}, options.headers);
451
            if (options.contentRange) {
452
                options.headers['Content-Range'] = options.contentRange;
453
            }
454
            if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
455
                options.headers['Content-Disposition'] = 'attachment; filename="' +
456
                    encodeURI(file.uploadName || file.name) + '"';
457
            }
458
            if (!multipart) {
459
                options.contentType = file.type || 'application/octet-stream';
460
                options.data = options.blob || file;
461
            } else if ($.support.xhrFormDataFileUpload) {
462
                if (options.postMessage) {
463
                    // window.postMessage does not allow sending FormData
464
                    // objects, so we just add the File/Blob objects to
465
                    // the formData array and let the postMessage window
466
                    // create the FormData object out of this array:
467
                    formData = this._getFormData(options);
468
                    if (options.blob) {
469
                        formData.push({
470
                            name: paramName,
471
                            value: options.blob
472
                        });
473
                    } else {
474
                        $.each(options.files, function (index, file) {
475
                            formData.push({
476
                                name: ($.type(options.paramName) === 'array' &&
477
                                    options.paramName[index]) || paramName,
478
                                value: file
479
                            });
480
                        });
481
                    }
482
                } else {
483
                    if (that._isInstanceOf('FormData', options.formData)) {
484
                        formData = options.formData;
485
                    } else {
486
                        formData = new FormData();
487
                        $.each(this._getFormData(options), function (index, field) {
488
                            formData.append(field.name, field.value);
489
                        });
490
                    }
491
                    if (options.blob) {
492
                        formData.append(
493
                            paramName,
494
                            options.blob,
495
                            file.uploadName || file.name
496
                        );
497
                    } else {
498
                        $.each(options.files, function (index, file) {
499
                            // This check allows the tests to run with
500
                            // dummy objects:
501
                            if (that._isInstanceOf('File', file) ||
502
                                    that._isInstanceOf('Blob', file)) {
503
                                formData.append(
504
                                    ($.type(options.paramName) === 'array' &&
505
                                        options.paramName[index]) || paramName,
506
                                    file,
507
                                    file.uploadName || file.name
508
                                );
509
                            }
510
                        });
511
                    }
512
                }
513
                options.data = formData;
514
            }
515
            // Blob reference is not needed anymore, free memory:
516
            options.blob = null;
517
        },
518
519
        _initIframeSettings: function (options) {
520
            var targetHost = $('<a></a>').prop('href', options.url).prop('host');
521
            // Setting the dataType to iframe enables the iframe transport:
522
            options.dataType = 'iframe ' + (options.dataType || '');
523
            // The iframe transport accepts a serialized array as form data:
524
            options.formData = this._getFormData(options);
525
            // Add redirect url to form data on cross-domain uploads:
526
            if (options.redirect && targetHost && targetHost !== location.host) {
527
                options.formData.push({
528
                    name: options.redirectParamName || 'redirect',
529
                    value: options.redirect
530
                });
531
            }
532
        },
533
534
        _initDataSettings: function (options) {
535
            if (this._isXHRUpload(options)) {
536
                if (!this._chunkedUpload(options, true)) {
537
                    if (!options.data) {
538
                        this._initXHRData(options);
539
                    }
540
                    this._initProgressListener(options);
541
                }
542
                if (options.postMessage) {
543
                    // Setting the dataType to postmessage enables the
544
                    // postMessage transport:
545
                    options.dataType = 'postmessage ' + (options.dataType || '');
546
                }
547
            } else {
548
                this._initIframeSettings(options);
549
            }
550
        },
551
552
        _getParamName: function (options) {
553
            var fileInput = $(options.fileInput),
554
                paramName = options.paramName;
555
            if (!paramName) {
556
                paramName = [];
557
                fileInput.each(function () {
558
                    var input = $(this),
559
                        name = input.prop('name') || 'files[]',
560
                        i = (input.prop('files') || [1]).length;
561
                    while (i) {
562
                        paramName.push(name);
563
                        i -= 1;
564
                    }
565
                });
566
                if (!paramName.length) {
567
                    paramName = [fileInput.prop('name') || 'files[]'];
568
                }
569
            } else if (!$.isArray(paramName)) {
570
                paramName = [paramName];
571
            }
572
            return paramName;
573
        },
574
575
        _initFormSettings: function (options) {
576
            // Retrieve missing options from the input field and the
577
            // associated form, if available:
578
            if (!options.form || !options.form.length) {
579
                options.form = $(options.fileInput.prop('form'));
580
                // If the given file input doesn't have an associated form,
581
                // use the default widget file input's form:
582
                if (!options.form.length) {
583
                    options.form = $(this.options.fileInput.prop('form'));
584
                }
585
            }
586
            options.paramName = this._getParamName(options);
587
            if (!options.url) {
588
                options.url = options.form.prop('action') || location.href;
589
            }
590
            // The HTTP request method must be "POST" or "PUT":
591
            options.type = (options.type ||
592
                ($.type(options.form.prop('method')) === 'string' &&
593
                    options.form.prop('method')) || ''
594
                ).toUpperCase();
595
            if (options.type !== 'POST' && options.type !== 'PUT' &&
596
                    options.type !== 'PATCH') {
597
                options.type = 'POST';
598
            }
599
            if (!options.formAcceptCharset) {
600
                options.formAcceptCharset = options.form.attr('accept-charset');
601
            }
602
        },
603
604
        _getAJAXSettings: function (data) {
605
            var options = $.extend({}, this.options, data);
606
            this._initFormSettings(options);
607
            this._initDataSettings(options);
608
            return options;
609
        },
610
611
        // jQuery 1.6 doesn't provide .state(),
612
        // while jQuery 1.8+ removed .isRejected() and .isResolved():
613
        _getDeferredState: function (deferred) {
614
            if (deferred.state) {
615
                return deferred.state();
616
            }
617
            if (deferred.isResolved()) {
618
                return 'resolved';
619
            }
620
            if (deferred.isRejected()) {
621
                return 'rejected';
622
            }
623
            return 'pending';
624
        },
625
626
        // Maps jqXHR callbacks to the equivalent
627
        // methods of the given Promise object:
628
        _enhancePromise: function (promise) {
629
            promise.success = promise.done;
630
            promise.error = promise.fail;
631
            promise.complete = promise.always;
632
            return promise;
633
        },
634
635
        // Creates and returns a Promise object enhanced with
636
        // the jqXHR methods abort, success, error and complete:
637
        _getXHRPromise: function (resolveOrReject, context, args) {
638
            var dfd = $.Deferred(),
639
                promise = dfd.promise();
640
            context = context || this.options.context || promise;
641
            if (resolveOrReject === true) {
642
                dfd.resolveWith(context, args);
643
            } else if (resolveOrReject === false) {
644
                dfd.rejectWith(context, args);
645
            }
646
            promise.abort = dfd.promise;
647
            return this._enhancePromise(promise);
648
        },
649
650
        // Adds convenience methods to the data callback argument:
651
        _addConvenienceMethods: function (e, data) {
652
            var that = this,
653
                getPromise = function (args) {
654
                    return $.Deferred().resolveWith(that, args).promise();
655
                };
656
            data.process = function (resolveFunc, rejectFunc) {
657
                if (resolveFunc || rejectFunc) {
658
                    data._processQueue = this._processQueue =
659
                        (this._processQueue || getPromise([this])).then(
660
                            function () {
661
                                if (data.errorThrown) {
662
                                    return $.Deferred()
663
                                        .rejectWith(that, [data]).promise();
664
                                }
665
                                return getPromise(arguments);
666
                            }
667
                        ).then(resolveFunc, rejectFunc);
668
                }
669
                return this._processQueue || getPromise([this]);
670
            };
671
            data.submit = function () {
672
                if (this.state() !== 'pending') {
673
                    data.jqXHR = this.jqXHR =
674
                        (that._trigger(
675
                            'submit',
676
                            $.Event('submit', {delegatedEvent: e}),
677
                            this
678
                        ) !== false) && that._onSend(e, this);
679
                }
680
                return this.jqXHR || that._getXHRPromise();
681
            };
682
            data.abort = function () {
683
                if (this.jqXHR) {
684
                    return this.jqXHR.abort();
685
                }
686
                this.errorThrown = 'abort';
687
                that._trigger('fail', null, this);
688
                return that._getXHRPromise(false);
689
            };
690
            data.state = function () {
691
                if (this.jqXHR) {
692
                    return that._getDeferredState(this.jqXHR);
693
                }
694
                if (this._processQueue) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if this._processQueue is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
695
                    return that._getDeferredState(this._processQueue);
696
                }
697
            };
698
            data.processing = function () {
699
                return !this.jqXHR && this._processQueue && that
700
                    ._getDeferredState(this._processQueue) === 'pending';
701
            };
702
            data.progress = function () {
703
                return this._progress;
704
            };
705
            data.response = function () {
706
                return this._response;
707
            };
708
        },
709
710
        // Parses the Range header from the server response
711
        // and returns the uploaded bytes:
712
        _getUploadedBytes: function (jqXHR) {
713
            var range = jqXHR.getResponseHeader('Range'),
714
                parts = range && range.split('-'),
715
                upperBytesPos = parts && parts.length > 1 &&
716
                    parseInt(parts[1], 10);
717
            return upperBytesPos && upperBytesPos + 1;
718
        },
719
720
        // Uploads a file in multiple, sequential requests
721
        // by splitting the file up in multiple blob chunks.
722
        // If the second parameter is true, only tests if the file
723
        // should be uploaded in chunks, but does not invoke any
724
        // upload requests:
725
        _chunkedUpload: function (options, testOnly) {
726
            options.uploadedBytes = options.uploadedBytes || 0;
727
            var that = this,
728
                file = options.files[0],
729
                fs = file.size,
730
                ub = options.uploadedBytes,
731
                mcs = options.maxChunkSize || fs,
732
                slice = this._blobSlice,
733
                dfd = $.Deferred(),
734
                promise = dfd.promise(),
735
                jqXHR,
736
                upload;
737
            if (!(this._isXHRUpload(options) && slice && (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)) ||
738
                    options.data) {
739
                return false;
740
            }
741
            if (testOnly) {
742
                return true;
743
            }
744
            if (ub >= fs) {
745
                file.error = options.i18n('uploadedBytes');
746
                return this._getXHRPromise(
747
                    false,
748
                    options.context,
749
                    [null, 'error', file.error]
750
                );
751
            }
752
            // The chunk upload method:
753
            upload = function () {
754
                // Clone the options object for each chunk upload:
755
                var o = $.extend({}, options),
756
                    currentLoaded = o._progress.loaded;
757
                o.blob = slice.call(
758
                    file,
759
                    ub,
760
                    ub + ($.type(mcs) === 'function' ? mcs(o) : mcs),
761
                    file.type
762
                );
763
                // Store the current chunk size, as the blob itself
764
                // will be dereferenced after data processing:
765
                o.chunkSize = o.blob.size;
766
                // Expose the chunk bytes position range:
767
                o.contentRange = 'bytes ' + ub + '-' +
768
                    (ub + o.chunkSize - 1) + '/' + fs;
769
                // Process the upload data (the blob and potential form data):
770
                that._initXHRData(o);
771
                // Add progress listeners for this chunk upload:
772
                that._initProgressListener(o);
773
                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
774
                        that._getXHRPromise(false, o.context))
775
                    .done(function (result, textStatus, jqXHR) {
776
                        ub = that._getUploadedBytes(jqXHR) ||
777
                            (ub + o.chunkSize);
778
                        // Create a progress event if no final progress event
779
                        // with loaded equaling total has been triggered
780
                        // for this chunk:
781
                        if (currentLoaded + o.chunkSize - o._progress.loaded) {
782
                            that._onProgress($.Event('progress', {
783
                                lengthComputable: true,
784
                                loaded: ub - o.uploadedBytes,
785
                                total: ub - o.uploadedBytes
786
                            }), o);
787
                        }
788
                        options.uploadedBytes = o.uploadedBytes = ub;
789
                        o.result = result;
790
                        o.textStatus = textStatus;
791
                        o.jqXHR = jqXHR;
792
                        that._trigger('chunkdone', null, o);
793
                        that._trigger('chunkalways', null, o);
794
                        if (ub < fs) {
795
                            // File upload not yet complete,
796
                            // continue with the next chunk:
797
                            upload();
798
                        } else {
799
                            dfd.resolveWith(
800
                                o.context,
801
                                [result, textStatus, jqXHR]
802
                            );
803
                        }
804
                    })
805
                    .fail(function (jqXHR, textStatus, errorThrown) {
806
                        o.jqXHR = jqXHR;
807
                        o.textStatus = textStatus;
808
                        o.errorThrown = errorThrown;
809
                        that._trigger('chunkfail', null, o);
810
                        that._trigger('chunkalways', null, o);
811
                        dfd.rejectWith(
812
                            o.context,
813
                            [jqXHR, textStatus, errorThrown]
814
                        );
815
                    });
816
            };
817
            this._enhancePromise(promise);
818
            promise.abort = function () {
819
                return jqXHR.abort();
820
            };
821
            upload();
822
            return promise;
823
        },
824
825
        _beforeSend: function (e, data) {
826
            if (this._active === 0) {
827
                // the start callback is triggered when an upload starts
828
                // and no other uploads are currently running,
829
                // equivalent to the global ajaxStart event:
830
                this._trigger('start');
831
                // Set timer for global bitrate progress calculation:
832
                this._bitrateTimer = new this._BitrateTimer();
833
                // Reset the global progress values:
834
                this._progress.loaded = this._progress.total = 0;
835
                this._progress.bitrate = 0;
836
            }
837
            // Make sure the container objects for the .response() and
838
            // .progress() methods on the data object are available
839
            // and reset to their initial state:
840
            this._initResponseObject(data);
841
            this._initProgressObject(data);
842
            data._progress.loaded = data.loaded = data.uploadedBytes || 0;
843
            data._progress.total = data.total = this._getTotal(data.files) || 1;
844
            data._progress.bitrate = data.bitrate = 0;
845
            this._active += 1;
846
            // Initialize the global progress values:
847
            this._progress.loaded += data.loaded;
848
            this._progress.total += data.total;
849
        },
850
851
        _onDone: function (result, textStatus, jqXHR, options) {
852
            var total = options._progress.total,
853
                response = options._response;
854
            if (options._progress.loaded < total) {
855
                // Create a progress event if no final progress event
856
                // with loaded equaling total has been triggered:
857
                this._onProgress($.Event('progress', {
858
                    lengthComputable: true,
859
                    loaded: total,
860
                    total: total
861
                }), options);
862
            }
863
            response.result = options.result = result;
864
            response.textStatus = options.textStatus = textStatus;
865
            response.jqXHR = options.jqXHR = jqXHR;
866
            this._trigger('done', null, options);
867
        },
868
869
        _onFail: function (jqXHR, textStatus, errorThrown, options) {
870
            var response = options._response;
871
            if (options.recalculateProgress) {
872
                // Remove the failed (error or abort) file upload from
873
                // the global progress calculation:
874
                this._progress.loaded -= options._progress.loaded;
875
                this._progress.total -= options._progress.total;
876
            }
877
            response.jqXHR = options.jqXHR = jqXHR;
878
            response.textStatus = options.textStatus = textStatus;
879
            response.errorThrown = options.errorThrown = errorThrown;
880
            this._trigger('fail', null, options);
881
        },
882
883
        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
884
            // jqXHRorResult, textStatus and jqXHRorError are added to the
885
            // options object via done and fail callbacks
886
            this._trigger('always', null, options);
887
        },
888
889
        _onSend: function (e, data) {
890
            if (!data.submit) {
891
                this._addConvenienceMethods(e, data);
892
            }
893
            var that = this,
894
                jqXHR,
895
                aborted,
896
                slot,
897
                pipe,
898
                options = that._getAJAXSettings(data),
899
                send = function () {
900
                    that._sending += 1;
901
                    // Set timer for bitrate progress calculation:
902
                    options._bitrateTimer = new that._BitrateTimer();
903
                    jqXHR = jqXHR || (
904
                        ((aborted || that._trigger(
905
                            'send',
906
                            $.Event('send', {delegatedEvent: e}),
907
                            options
908
                        ) === false) &&
909
                        that._getXHRPromise(false, options.context, aborted)) ||
0 ignored issues
show
Bug introduced by
The variable aborted seems to not be initialized for all possible execution paths. Are you sure _getXHRPromise handles undefined variables?
Loading history...
910
                        that._chunkedUpload(options) || $.ajax(options)
911
                    ).done(function (result, textStatus, jqXHR) {
912
                        that._onDone(result, textStatus, jqXHR, options);
913
                    }).fail(function (jqXHR, textStatus, errorThrown) {
914
                        that._onFail(jqXHR, textStatus, errorThrown, options);
915
                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
916
                        that._onAlways(
917
                            jqXHRorResult,
918
                            textStatus,
919
                            jqXHRorError,
920
                            options
921
                        );
922
                        that._sending -= 1;
923
                        that._active -= 1;
924
                        if (options.limitConcurrentUploads &&
925
                                options.limitConcurrentUploads > that._sending) {
926
                            // Start the next queued upload,
927
                            // that has not been aborted:
928
                            var nextSlot = that._slots.shift();
929
                            while (nextSlot) {
930
                                if (that._getDeferredState(nextSlot) === 'pending') {
931
                                    nextSlot.resolve();
932
                                    break;
933
                                }
934
                                nextSlot = that._slots.shift();
935
                            }
936
                        }
937
                        if (that._active === 0) {
938
                            // The stop callback is triggered when all uploads have
939
                            // been completed, equivalent to the global ajaxStop event:
940
                            that._trigger('stop');
941
                        }
942
                    });
943
                    return jqXHR;
944
                };
945
            this._beforeSend(e, options);
946
            if (this.options.sequentialUploads ||
947
                    (this.options.limitConcurrentUploads &&
948
                    this.options.limitConcurrentUploads <= this._sending)) {
949
                if (this.options.limitConcurrentUploads > 1) {
950
                    slot = $.Deferred();
951
                    this._slots.push(slot);
952
                    pipe = slot.then(send);
953
                } else {
954
                    this._sequence = this._sequence.then(send, send);
955
                    pipe = this._sequence;
956
                }
957
                // Return the piped Promise object, enhanced with an abort method,
958
                // which is delegated to the jqXHR object of the current upload,
959
                // and jqXHR callbacks mapped to the equivalent Promise methods:
960
                pipe.abort = function () {
961
                    aborted = [undefined, 'abort', 'abort'];
962
                    if (!jqXHR) {
963
                        if (slot) {
964
                            slot.rejectWith(options.context, aborted);
965
                        }
966
                        return send();
967
                    }
968
                    return jqXHR.abort();
969
                };
970
                return this._enhancePromise(pipe);
971
            }
972
            return send();
973
        },
974
975
        _onAdd: function (e, data) {
976
            var that = this,
977
                result = true,
978
                options = $.extend({}, this.options, data),
979
                files = data.files,
980
                filesLength = files.length,
981
                limit = options.limitMultiFileUploads,
982
                limitSize = options.limitMultiFileUploadSize,
983
                overhead = options.limitMultiFileUploadSizeOverhead,
984
                batchSize = 0,
985
                paramName = this._getParamName(options),
986
                paramNameSet,
987
                paramNameSlice,
988
                fileSet,
989
                i,
990
                j = 0;
991
            if (!filesLength) {
992
                return false;
993
            }
994
            if (limitSize && files[0].size === undefined) {
995
                limitSize = undefined;
996
            }
997
            if (!(options.singleFileUploads || limit || limitSize) ||
998
                    !this._isXHRUpload(options)) {
999
                fileSet = [files];
1000
                paramNameSet = [paramName];
1001
            } else if (!(options.singleFileUploads || limitSize) && limit) {
1002
                fileSet = [];
1003
                paramNameSet = [];
1004
                for (i = 0; i < filesLength; i += limit) {
1005
                    fileSet.push(files.slice(i, i + limit));
1006
                    paramNameSlice = paramName.slice(i, i + limit);
1007
                    if (!paramNameSlice.length) {
1008
                        paramNameSlice = paramName;
1009
                    }
1010
                    paramNameSet.push(paramNameSlice);
1011
                }
1012
            } else if (!options.singleFileUploads && limitSize) {
1013
                fileSet = [];
1014
                paramNameSet = [];
1015
                for (i = 0; i < filesLength; i = i + 1) {
1016
                    batchSize += files[i].size + overhead;
1017
                    if (i + 1 === filesLength ||
1018
                            ((batchSize + files[i + 1].size + overhead) > limitSize) ||
1019
                            (limit && i + 1 - j >= limit)) {
1020
                        fileSet.push(files.slice(j, i + 1));
1021
                        paramNameSlice = paramName.slice(j, i + 1);
1022
                        if (!paramNameSlice.length) {
1023
                            paramNameSlice = paramName;
1024
                        }
1025
                        paramNameSet.push(paramNameSlice);
1026
                        j = i + 1;
1027
                        batchSize = 0;
1028
                    }
1029
                }
1030
            } else {
1031
                paramNameSet = paramName;
1032
            }
1033
            data.originalFiles = files;
1034
            $.each(fileSet || files, function (index, element) {
1035
                var newData = $.extend({}, data);
1036
                newData.files = fileSet ? element : [element];
1037
                newData.paramName = paramNameSet[index];
1038
                that._initResponseObject(newData);
1039
                that._initProgressObject(newData);
1040
                that._addConvenienceMethods(e, newData);
1041
                result = that._trigger(
1042
                    'add',
1043
                    $.Event('add', {delegatedEvent: e}),
1044
                    newData
1045
                );
1046
                return result;
1047
            });
1048
            return result;
1049
        },
1050
1051
        _replaceFileInput: function (data) {
1052
            var input = data.fileInput,
1053
                inputClone = input.clone(true),
1054
                restoreFocus = input.is(document.activeElement);
1055
            // Add a reference for the new cloned file input to the data argument:
1056
            data.fileInputClone = inputClone;
1057
            $('<form></form>').append(inputClone)[0].reset();
1058
            // Detaching allows to insert the fileInput on another form
1059
            // without loosing the file input value:
1060
            input.after(inputClone).detach();
1061
            // If the fileInput had focus before it was detached,
1062
            // restore focus to the inputClone.
1063
            if (restoreFocus) {
1064
                inputClone.focus();
1065
            }
1066
            // Avoid memory leaks with the detached file input:
1067
            $.cleanData(input.unbind('remove'));
1068
            // Replace the original file input element in the fileInput
1069
            // elements set with the clone, which has been copied including
1070
            // event handlers:
1071
            this.options.fileInput = this.options.fileInput.map(function (i, el) {
1072
                if (el === input[0]) {
1073
                    return inputClone[0];
1074
                }
1075
                return el;
1076
            });
1077
            // If the widget has been initialized on the file input itself,
1078
            // override this.element with the file input clone:
1079
            if (input[0] === this.element[0]) {
1080
                this.element = inputClone;
1081
            }
1082
        },
1083
1084
        _handleFileTreeEntry: function (entry, path) {
1085
            var that = this,
1086
                dfd = $.Deferred(),
1087
                entries = [],
1088
                dirReader,
1089
                errorHandler = function (e) {
1090
                    if (e && !e.entry) {
1091
                        e.entry = entry;
1092
                    }
1093
                    // Since $.when returns immediately if one
1094
                    // Deferred is rejected, we use resolve instead.
1095
                    // This allows valid files and invalid items
1096
                    // to be returned together in one set:
1097
                    dfd.resolve([e]);
1098
                },
1099
                successHandler = function (entries) {
1100
                    that._handleFileTreeEntries(
1101
                        entries,
1102
                        path + entry.name + '/'
1103
                    ).done(function (files) {
1104
                        dfd.resolve(files);
1105
                    }).fail(errorHandler);
1106
                },
1107
                readEntries = function () {
1108
                    dirReader.readEntries(function (results) {
1109
                        if (!results.length) {
1110
                            successHandler(entries);
1111
                        } else {
1112
                            entries = entries.concat(results);
1113
                            readEntries();
1114
                        }
1115
                    }, errorHandler);
1116
                };
1117
            path = path || '';
1118
            if (entry.isFile) {
1119
                if (entry._file) {
1120
                    // Workaround for Chrome bug #149735
1121
                    entry._file.relativePath = path;
1122
                    dfd.resolve(entry._file);
1123
                } else {
1124
                    entry.file(function (file) {
1125
                        file.relativePath = path;
1126
                        dfd.resolve(file);
1127
                    }, errorHandler);
1128
                }
1129
            } else if (entry.isDirectory) {
1130
                dirReader = entry.createReader();
1131
                readEntries();
1132
            } else {
1133
                // Return an empy list for file system items
1134
                // other than files or directories:
1135
                dfd.resolve([]);
1136
            }
1137
            return dfd.promise();
1138
        },
1139
1140
        _handleFileTreeEntries: function (entries, path) {
1141
            var that = this;
1142
            return $.when.apply(
1143
                $,
1144
                $.map(entries, function (entry) {
1145
                    return that._handleFileTreeEntry(entry, path);
1146
                })
1147
            ).then(function () {
1148
                return Array.prototype.concat.apply(
1149
                    [],
1150
                    arguments
1151
                );
1152
            });
1153
        },
1154
1155
        _getDroppedFiles: function (dataTransfer) {
1156
            dataTransfer = dataTransfer || {};
1157
            var items = dataTransfer.items;
1158
            if (items && items.length && (items[0].webkitGetAsEntry ||
1159
                    items[0].getAsEntry)) {
1160
                return this._handleFileTreeEntries(
1161
                    $.map(items, function (item) {
1162
                        var entry;
1163
                        if (item.webkitGetAsEntry) {
1164
                            entry = item.webkitGetAsEntry();
1165
                            if (entry) {
1166
                                // Workaround for Chrome bug #149735:
1167
                                entry._file = item.getAsFile();
1168
                            }
1169
                            return entry;
1170
                        }
1171
                        return item.getAsEntry();
1172
                    })
1173
                );
1174
            }
1175
            return $.Deferred().resolve(
1176
                $.makeArray(dataTransfer.files)
1177
            ).promise();
1178
        },
1179
1180
        _getSingleFileInputFiles: function (fileInput) {
1181
            fileInput = $(fileInput);
1182
            var entries = fileInput.prop('webkitEntries') ||
1183
                    fileInput.prop('entries'),
1184
                files,
1185
                value;
1186
            if (entries && entries.length) {
1187
                return this._handleFileTreeEntries(entries);
1188
            }
1189
            files = $.makeArray(fileInput.prop('files'));
1190
            if (!files.length) {
1191
                value = fileInput.prop('value');
1192
                if (!value) {
1193
                    return $.Deferred().resolve([]).promise();
1194
                }
1195
                // If the files property is not available, the browser does not
1196
                // support the File API and we add a pseudo File object with
1197
                // the input value as name with path information removed:
1198
                files = [{name: value.replace(/^.*\\/, '')}];
1199
            } else if (files[0].name === undefined && files[0].fileName) {
1200
                // File normalization for Safari 4 and Firefox 3:
1201
                $.each(files, function (index, file) {
1202
                    file.name = file.fileName;
1203
                    file.size = file.fileSize;
1204
                });
1205
            }
1206
            return $.Deferred().resolve(files).promise();
1207
        },
1208
1209
        _getFileInputFiles: function (fileInput) {
1210
            if (!(fileInput instanceof $) || fileInput.length === 1) {
1211
                return this._getSingleFileInputFiles(fileInput);
1212
            }
1213
            return $.when.apply(
1214
                $,
1215
                $.map(fileInput, this._getSingleFileInputFiles)
1216
            ).then(function () {
1217
                return Array.prototype.concat.apply(
1218
                    [],
1219
                    arguments
1220
                );
1221
            });
1222
        },
1223
1224
        _onChange: function (e) {
1225
            var that = this,
1226
                data = {
1227
                    fileInput: $(e.target),
1228
                    form: $(e.target.form)
1229
                };
1230
            this._getFileInputFiles(data.fileInput).always(function (files) {
1231
                data.files = files;
1232
                if (that.options.replaceFileInput) {
1233
                    that._replaceFileInput(data);
1234
                }
1235
                if (that._trigger(
1236
                        'change',
1237
                        $.Event('change', {delegatedEvent: e}),
1238
                        data
1239
                    ) !== false) {
1240
                    that._onAdd(e, data);
1241
                }
1242
            });
1243
        },
1244
1245
        _onPaste: function (e) {
1246
            var items = e.originalEvent && e.originalEvent.clipboardData &&
1247
                    e.originalEvent.clipboardData.items,
1248
                data = {files: []};
1249
            if (items && items.length) {
1250
                $.each(items, function (index, item) {
1251
                    var file = item.getAsFile && item.getAsFile();
1252
                    if (file) {
1253
                        data.files.push(file);
1254
                    }
1255
                });
1256
                if (this._trigger(
1257
                        'paste',
1258
                        $.Event('paste', {delegatedEvent: e}),
1259
                        data
1260
                    ) !== false) {
1261
                    this._onAdd(e, data);
1262
                }
1263
            }
1264
        },
1265
1266
        _onDrop: function (e) {
1267
            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1268
            var that = this,
1269
                dataTransfer = e.dataTransfer,
1270
                data = {};
1271
            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1272
                e.preventDefault();
1273
                this._getDroppedFiles(dataTransfer).always(function (files) {
1274
                    data.files = files;
1275
                    if (that._trigger(
1276
                            'drop',
1277
                            $.Event('drop', {delegatedEvent: e}),
1278
                            data
1279
                        ) !== false) {
1280
                        that._onAdd(e, data);
1281
                    }
1282
                });
1283
            }
1284
        },
1285
1286
        _onDragOver: getDragHandler('dragover'),
1287
1288
        _onDragEnter: getDragHandler('dragenter'),
1289
1290
        _onDragLeave: getDragHandler('dragleave'),
1291
1292
        _initEventHandlers: function () {
1293
            if (this._isXHRUpload(this.options)) {
1294
                this._on(this.options.dropZone, {
1295
                    dragover: this._onDragOver,
1296
                    drop: this._onDrop,
1297
                    // event.preventDefault() on dragenter is required for IE10+:
1298
                    dragenter: this._onDragEnter,
1299
                    // dragleave is not required, but added for completeness:
1300
                    dragleave: this._onDragLeave
1301
                });
1302
                this._on(this.options.pasteZone, {
1303
                    paste: this._onPaste
1304
                });
1305
            }
1306
            if ($.support.fileInput) {
1307
                this._on(this.options.fileInput, {
1308
                    change: this._onChange
1309
                });
1310
            }
1311
        },
1312
1313
        _destroyEventHandlers: function () {
1314
            this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
1315
            this._off(this.options.pasteZone, 'paste');
1316
            this._off(this.options.fileInput, 'change');
1317
        },
1318
1319
        _destroy: function () {
1320
            this._destroyEventHandlers();
1321
        },
1322
1323
        _setOption: function (key, value) {
1324
            var reinit = $.inArray(key, this._specialOptions) !== -1;
1325
            if (reinit) {
1326
                this._destroyEventHandlers();
1327
            }
1328
            this._super(key, value);
1329
            if (reinit) {
1330
                this._initSpecialOptions();
1331
                this._initEventHandlers();
1332
            }
1333
        },
1334
1335
        _initSpecialOptions: function () {
1336
            var options = this.options;
1337
            if (options.fileInput === undefined) {
1338
                options.fileInput = this.element.is('input[type="file"]') ?
1339
                        this.element : this.element.find('input[type="file"]');
1340
            } else if (!(options.fileInput instanceof $)) {
1341
                options.fileInput = $(options.fileInput);
1342
            }
1343
            if (!(options.dropZone instanceof $)) {
1344
                options.dropZone = $(options.dropZone);
1345
            }
1346
            if (!(options.pasteZone instanceof $)) {
1347
                options.pasteZone = $(options.pasteZone);
1348
            }
1349
        },
1350
1351
        _getRegExp: function (str) {
1352
            var parts = str.split('/'),
1353
                modifiers = parts.pop();
1354
            parts.shift();
1355
            return new RegExp(parts.join('/'), modifiers);
1356
        },
1357
1358
        _isRegExpOption: function (key, value) {
1359
            return key !== 'url' && $.type(value) === 'string' &&
1360
                /^\/.*\/[igm]{0,3}$/.test(value);
1361
        },
1362
1363
        _initDataAttributes: function () {
1364
            var that = this,
1365
                options = this.options,
1366
                data = this.element.data();
1367
            // Initialize options set via HTML5 data-attributes:
1368
            $.each(
1369
                this.element[0].attributes,
1370
                function (index, attr) {
1371
                    var key = attr.name.toLowerCase(),
1372
                        value;
1373
                    if (/^data-/.test(key)) {
1374
                        // Convert hyphen-ated key to camelCase:
1375
                        key = key.slice(5).replace(/-[a-z]/g, function (str) {
1376
                            return str.charAt(1).toUpperCase();
1377
                        });
1378
                        value = data[key];
1379
                        if (that._isRegExpOption(key, value)) {
1380
                            value = that._getRegExp(value);
1381
                        }
1382
                        options[key] = value;
1383
                    }
1384
                }
1385
            );
1386
        },
1387
1388
        _create: function () {
1389
            this._initDataAttributes();
1390
            this._initSpecialOptions();
1391
            this._slots = [];
1392
            this._sequence = this._getXHRPromise(true);
1393
            this._sending = this._active = 0;
1394
            this._initProgressObject(this);
1395
            this._initEventHandlers();
1396
        },
1397
1398
        // This method is exposed to the widget API and allows to query
1399
        // the number of active uploads:
1400
        active: function () {
1401
            return this._active;
1402
        },
1403
1404
        // This method is exposed to the widget API and allows to query
1405
        // the widget upload progress.
1406
        // It returns an object with loaded, total and bitrate properties
1407
        // for the running uploads:
1408
        progress: function () {
1409
            return this._progress;
1410
        },
1411
1412
        // This method is exposed to the widget API and allows adding files
1413
        // using the fileupload API. The data parameter accepts an object which
1414
        // must have a files property and can contain additional options:
1415
        // .fileupload('add', {files: filesList});
1416
        add: function (data) {
1417
            var that = this;
1418
            if (!data || this.options.disabled) {
1419
                return;
1420
            }
1421
            if (data.fileInput && !data.files) {
1422
                this._getFileInputFiles(data.fileInput).always(function (files) {
1423
                    data.files = files;
1424
                    that._onAdd(null, data);
1425
                });
1426
            } else {
1427
                data.files = $.makeArray(data.files);
1428
                this._onAdd(null, data);
1429
            }
1430
        },
1431
1432
        // This method is exposed to the widget API and allows sending files
1433
        // using the fileupload API. The data parameter accepts an object which
1434
        // must have a files or fileInput property and can contain additional options:
1435
        // .fileupload('send', {files: filesList});
1436
        // The method returns a Promise object for the file upload call.
1437
        send: function (data) {
1438
            if (data && !this.options.disabled) {
1439
                if (data.fileInput && !data.files) {
1440
                    var that = this,
1441
                        dfd = $.Deferred(),
1442
                        promise = dfd.promise(),
1443
                        jqXHR,
1444
                        aborted;
1445
                    promise.abort = function () {
1446
                        aborted = true;
1447
                        if (jqXHR) {
1448
                            return jqXHR.abort();
1449
                        }
1450
                        dfd.reject(null, 'abort', 'abort');
1451
                        return promise;
1452
                    };
1453
                    this._getFileInputFiles(data.fileInput).always(
1454
                        function (files) {
1455
                            if (aborted) {
1456
                                return;
1457
                            }
1458
                            if (!files.length) {
1459
                                dfd.reject();
1460
                                return;
1461
                            }
1462
                            data.files = files;
1463
                            jqXHR = that._onSend(null, data);
1464
                            jqXHR.then(
1465
                                function (result, textStatus, jqXHR) {
1466
                                    dfd.resolve(result, textStatus, jqXHR);
1467
                                },
1468
                                function (jqXHR, textStatus, errorThrown) {
1469
                                    dfd.reject(jqXHR, textStatus, errorThrown);
1470
                                }
1471
                            );
1472
                        }
1473
                    );
1474
                    return this._enhancePromise(promise);
1475
                }
1476
                data.files = $.makeArray(data.files);
1477
                if (data.files.length) {
1478
                    return this._onSend(null, data);
1479
                }
1480
            }
1481
            return this._getXHRPromise(false, data && data.context);
1482
        }
1483
1484
    });
1485
1486
}));
1487