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

Complexity

Total Complexity 137
Complexity/F 1.85

Size

Lines of Code 700
Function Count 74

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 679477248
dl 0
loc 700
rs 2.1818
c 0
b 0
f 0
wmc 137
mnd 3
bc 131
fnc 74
bpm 1.7702
cpm 1.8513
noi 1

1 Function

Rating   Name   Duplication   Size   Complexity  
B jquery.fileupload-ui.js ➔ ?!? 0 671 1

How to fix   Complexity   

Complexity

Complex classes like resources/lib/blueimp-file-upload/js/jquery.fileupload-ui.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 User Interface 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 */
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
            'blueimp-tmpl',
22
            './jquery.fileupload-image',
23
            './jquery.fileupload-audio',
24
            './jquery.fileupload-video',
25
            './jquery.fileupload-validate'
26
        ], factory);
27
    } else if (typeof exports === 'object') {
28
        // Node/CommonJS:
29
        factory(
30
            require('jquery'),
31
            require('blueimp-tmpl'),
32
            require('./jquery.fileupload-image'),
33
            require('./jquery.fileupload-audio'),
34
            require('./jquery.fileupload-video'),
35
            require('./jquery.fileupload-validate')
36
        );
37
    } else {
38
        // Browser globals:
39
        factory(
40
            window.jQuery,
41
            window.tmpl
42
        );
43
    }
44
}(function ($, tmpl) {
45
    'use strict';
46
47
    $.blueimp.fileupload.prototype._specialOptions.push(
48
        'filesContainer',
49
        'uploadTemplateId',
50
        'downloadTemplateId'
51
    );
52
53
    // The UI version extends the file upload widget
54
    // and adds complete user interface interaction:
55
    $.widget('blueimp.fileupload', $.blueimp.fileupload, {
56
57
        options: {
58
            // By default, files added to the widget are uploaded as soon
59
            // as the user clicks on the start buttons. To enable automatic
60
            // uploads, set the following option to true:
61
            autoUpload: false,
62
            // The ID of the upload template:
63
            uploadTemplateId: 'template-upload',
64
            // The ID of the download template:
65
            downloadTemplateId: 'template-download',
66
            // The container for the list of files. If undefined, it is set to
67
            // an element with class "files" inside of the widget element:
68
            filesContainer: undefined,
69
            // By default, files are appended to the files container.
70
            // Set the following option to true, to prepend files instead:
71
            prependFiles: false,
72
            // The expected data type of the upload response, sets the dataType
73
            // option of the $.ajax upload requests:
74
            dataType: 'json',
75
76
            // Error and info messages:
77
            messages: {
78
                unknownError: 'Unknown error'
79
            },
80
81
            // Function returning the current number of files,
82
            // used by the maxNumberOfFiles validation:
83
            getNumberOfFiles: function () {
84
                return this.filesContainer.children()
85
                    .not('.processing').length;
86
            },
87
88
            // Callback to retrieve the list of files from the server response:
89
            getFilesFromResponse: function (data) {
90
                if (data.result && $.isArray(data.result.files)) {
91
                    return data.result.files;
92
                }
93
                return [];
94
            },
95
96
            // The add callback is invoked as soon as files are added to the fileupload
97
            // widget (via file input selection, drag & drop or add API call).
98
            // See the basic file upload widget for more information:
99
            add: function (e, data) {
100
                if (e.isDefaultPrevented()) {
101
                    return false;
102
                }
103
                var $this = $(this),
104
                    that = $this.data('blueimp-fileupload') ||
105
                        $this.data('fileupload'),
106
                    options = that.options;
107
                data.context = that._renderUpload(data.files)
108
                    .data('data', data)
109
                    .addClass('processing');
110
                options.filesContainer[
111
                    options.prependFiles ? 'prepend' : 'append'
112
                ](data.context);
113
                that._forceReflow(data.context);
114
                that._transition(data.context);
115
                data.process(function () {
116
                    return $this.fileupload('process', data);
117
                }).always(function () {
118
                    data.context.each(function (index) {
119
                        $(this).find('.size').text(
120
                            that._formatFileSize(data.files[index].size)
121
                        );
122
                    }).removeClass('processing');
123
                    that._renderPreviews(data);
124
                }).done(function () {
125
                    data.context.find('.start').prop('disabled', false);
126
                    if ((that._trigger('added', e, data) !== false) &&
127
                            (options.autoUpload || data.autoUpload) &&
128
                            data.autoUpload !== false) {
129
                        data.submit();
130
                    }
131
                }).fail(function () {
132
                    if (data.files.error) {
133
                        data.context.each(function (index) {
134
                            var error = data.files[index].error;
135
                            if (error) {
136
                                $(this).find('.error').text(error);
137
                            }
138
                        });
139
                    }
140
                });
141
            },
142
            // Callback for the start of each file upload request:
143
            send: function (e, data) {
144
                if (e.isDefaultPrevented()) {
145
                    return false;
146
                }
147
                var that = $(this).data('blueimp-fileupload') ||
148
                        $(this).data('fileupload');
149
                if (data.context && data.dataType &&
150
                        data.dataType.substr(0, 6) === 'iframe') {
151
                    // Iframe Transport does not support progress events.
152
                    // In lack of an indeterminate progress bar, we set
153
                    // the progress to 100%, showing the full animated bar:
154
                    data.context
155
                        .find('.progress').addClass(
156
                            !$.support.transition && 'progress-animated'
157
                        )
158
                        .attr('aria-valuenow', 100)
159
                        .children().first().css(
160
                            'width',
161
                            '100%'
162
                        );
163
                }
164
                return that._trigger('sent', e, data);
165
            },
166
            // Callback for successful uploads:
167
            done: function (e, data) {
168
                if (e.isDefaultPrevented()) {
169
                    return false;
170
                }
171
                var that = $(this).data('blueimp-fileupload') ||
172
                        $(this).data('fileupload'),
173
                    getFilesFromResponse = data.getFilesFromResponse ||
174
                        that.options.getFilesFromResponse,
175
                    files = getFilesFromResponse(data),
176
                    template,
177
                    deferred;
178
                if (data.context) {
179
                    data.context.each(function (index) {
180
                        var file = files[index] ||
181
                                {error: 'Empty file upload result'};
182
                        deferred = that._addFinishedDeferreds();
183
                        that._transition($(this)).done(
184
                            function () {
185
                                var node = $(this);
186
                                template = that._renderDownload([file])
187
                                    .replaceAll(node);
188
                                that._forceReflow(template);
189
                                that._transition(template).done(
190
                                    function () {
191
                                        data.context = $(this);
192
                                        that._trigger('completed', e, data);
193
                                        that._trigger('finished', e, data);
194
                                        deferred.resolve();
195
                                    }
196
                                );
197
                            }
198
                        );
199
                    });
200
                } else {
201
                    template = that._renderDownload(files)[
202
                        that.options.prependFiles ? 'prependTo' : 'appendTo'
203
                    ](that.options.filesContainer);
204
                    that._forceReflow(template);
205
                    deferred = that._addFinishedDeferreds();
206
                    that._transition(template).done(
207
                        function () {
208
                            data.context = $(this);
209
                            that._trigger('completed', e, data);
210
                            that._trigger('finished', e, data);
211
                            deferred.resolve();
212
                        }
213
                    );
214
                }
215
            },
216
            // Callback for failed (abort or error) uploads:
217
            fail: function (e, data) {
218
                if (e.isDefaultPrevented()) {
219
                    return false;
220
                }
221
                var that = $(this).data('blueimp-fileupload') ||
222
                        $(this).data('fileupload'),
223
                    template,
224
                    deferred;
225
                if (data.context) {
226
                    data.context.each(function (index) {
227
                        if (data.errorThrown !== 'abort') {
228
                            var file = data.files[index];
229
                            file.error = file.error || data.errorThrown ||
230
                                data.i18n('unknownError');
231
                            deferred = that._addFinishedDeferreds();
232
                            that._transition($(this)).done(
233
                                function () {
234
                                    var node = $(this);
235
                                    template = that._renderDownload([file])
236
                                        .replaceAll(node);
237
                                    that._forceReflow(template);
238
                                    that._transition(template).done(
239
                                        function () {
240
                                            data.context = $(this);
241
                                            that._trigger('failed', e, data);
242
                                            that._trigger('finished', e, data);
243
                                            deferred.resolve();
244
                                        }
245
                                    );
246
                                }
247
                            );
248
                        } else {
249
                            deferred = that._addFinishedDeferreds();
250
                            that._transition($(this)).done(
251
                                function () {
252
                                    $(this).remove();
253
                                    that._trigger('failed', e, data);
254
                                    that._trigger('finished', e, data);
255
                                    deferred.resolve();
256
                                }
257
                            );
258
                        }
259
                    });
260
                } else if (data.errorThrown !== 'abort') {
261
                    data.context = that._renderUpload(data.files)[
262
                        that.options.prependFiles ? 'prependTo' : 'appendTo'
263
                    ](that.options.filesContainer)
264
                        .data('data', data);
265
                    that._forceReflow(data.context);
266
                    deferred = that._addFinishedDeferreds();
267
                    that._transition(data.context).done(
268
                        function () {
269
                            data.context = $(this);
270
                            that._trigger('failed', e, data);
271
                            that._trigger('finished', e, data);
272
                            deferred.resolve();
273
                        }
274
                    );
275
                } else {
276
                    that._trigger('failed', e, data);
277
                    that._trigger('finished', e, data);
278
                    that._addFinishedDeferreds().resolve();
279
                }
280
            },
281
            // Callback for upload progress events:
282
            progress: function (e, data) {
283
                if (e.isDefaultPrevented()) {
284
                    return false;
285
                }
286
                var progress = Math.floor(data.loaded / data.total * 100);
287
                if (data.context) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if data.context 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...
288
                    data.context.each(function () {
289
                        $(this).find('.progress')
290
                            .attr('aria-valuenow', progress)
291
                            .children().first().css(
292
                                'width',
293
                                progress + '%'
294
                            );
295
                    });
296
                }
297
            },
298
            // Callback for global upload progress events:
299
            progressall: function (e, data) {
300
                if (e.isDefaultPrevented()) {
301
                    return false;
302
                }
303
                var $this = $(this),
304
                    progress = Math.floor(data.loaded / data.total * 100),
305
                    globalProgressNode = $this.find('.fileupload-progress'),
306
                    extendedProgressNode = globalProgressNode
307
                        .find('.progress-extended');
308
                if (extendedProgressNode.length) {
309
                    extendedProgressNode.html(
310
                        ($this.data('blueimp-fileupload') || $this.data('fileupload'))
311
                            ._renderExtendedProgress(data)
312
                    );
313
                }
314
                globalProgressNode
315
                    .find('.progress')
316
                    .attr('aria-valuenow', progress)
317
                    .children().first().css(
318
                        'width',
319
                        progress + '%'
320
                    );
321
            },
322
            // Callback for uploads start, equivalent to the global ajaxStart event:
323
            start: function (e) {
324
                if (e.isDefaultPrevented()) {
325
                    return false;
326
                }
327
                var that = $(this).data('blueimp-fileupload') ||
328
                        $(this).data('fileupload');
329
                that._resetFinishedDeferreds();
330
                that._transition($(this).find('.fileupload-progress')).done(
331
                    function () {
332
                        that._trigger('started', e);
333
                    }
334
                );
335
            },
336
            // Callback for uploads stop, equivalent to the global ajaxStop event:
337
            stop: function (e) {
338
                if (e.isDefaultPrevented()) {
339
                    return false;
340
                }
341
                var that = $(this).data('blueimp-fileupload') ||
342
                        $(this).data('fileupload'),
343
                    deferred = that._addFinishedDeferreds();
344
                $.when.apply($, that._getFinishedDeferreds())
345
                    .done(function () {
346
                        that._trigger('stopped', e);
347
                    });
348
                that._transition($(this).find('.fileupload-progress')).done(
349
                    function () {
350
                        $(this).find('.progress')
351
                            .attr('aria-valuenow', '0')
352
                            .children().first().css('width', '0%');
353
                        $(this).find('.progress-extended').html(' ');
354
                        deferred.resolve();
355
                    }
356
                );
357
            },
358
            processstart: function (e) {
359
                if (e.isDefaultPrevented()) {
360
                    return false;
361
                }
362
                $(this).addClass('fileupload-processing');
363
            },
364
            processstop: function (e) {
365
                if (e.isDefaultPrevented()) {
366
                    return false;
367
                }
368
                $(this).removeClass('fileupload-processing');
369
            },
370
            // Callback for file deletion:
371
            destroy: function (e, data) {
372
                if (e.isDefaultPrevented()) {
373
                    return false;
374
                }
375
                var that = $(this).data('blueimp-fileupload') ||
376
                        $(this).data('fileupload'),
377
                    removeNode = function () {
378
                        that._transition(data.context).done(
379
                            function () {
380
                                $(this).remove();
381
                                that._trigger('destroyed', e, data);
382
                            }
383
                        );
384
                    };
385
                if (data.url) {
386
                    data.dataType = data.dataType || that.options.dataType;
387
                    $.ajax(data).done(removeNode).fail(function () {
388
                        that._trigger('destroyfailed', e, data);
389
                    });
390
                } else {
391
                    removeNode();
392
                }
393
            }
394
        },
395
396
        _resetFinishedDeferreds: function () {
397
            this._finishedUploads = [];
398
        },
399
400
        _addFinishedDeferreds: function (deferred) {
401
            if (!deferred) {
402
                deferred = $.Deferred();
403
            }
404
            this._finishedUploads.push(deferred);
405
            return deferred;
406
        },
407
408
        _getFinishedDeferreds: function () {
409
            return this._finishedUploads;
410
        },
411
412
        // Link handler, that allows to download files
413
        // by drag & drop of the links to the desktop:
414
        _enableDragToDesktop: function () {
415
            var link = $(this),
416
                url = link.prop('href'),
417
                name = link.prop('download'),
418
                type = 'application/octet-stream';
419
            link.bind('dragstart', function (e) {
420
                try {
421
                    e.originalEvent.dataTransfer.setData(
422
                        'DownloadURL',
423
                        [type, name, url].join(':')
424
                    );
425
                } catch (ignore) {}
426
            });
427
        },
428
429
        _formatFileSize: function (bytes) {
430
            if (typeof bytes !== 'number') {
431
                return '';
432
            }
433
            if (bytes >= 1000000000) {
434
                return (bytes / 1000000000).toFixed(2) + ' GB';
435
            }
436
            if (bytes >= 1000000) {
437
                return (bytes / 1000000).toFixed(2) + ' MB';
438
            }
439
            return (bytes / 1000).toFixed(2) + ' KB';
440
        },
441
442
        _formatBitrate: function (bits) {
443
            if (typeof bits !== 'number') {
444
                return '';
445
            }
446
            if (bits >= 1000000000) {
447
                return (bits / 1000000000).toFixed(2) + ' Gbit/s';
448
            }
449
            if (bits >= 1000000) {
450
                return (bits / 1000000).toFixed(2) + ' Mbit/s';
451
            }
452
            if (bits >= 1000) {
453
                return (bits / 1000).toFixed(2) + ' kbit/s';
454
            }
455
            return bits.toFixed(2) + ' bit/s';
456
        },
457
458
        _formatTime: function (seconds) {
459
            var date = new Date(seconds * 1000),
460
                days = Math.floor(seconds / 86400);
461
            days = days ? days + 'd ' : '';
462
            return days +
463
                ('0' + date.getUTCHours()).slice(-2) + ':' +
464
                ('0' + date.getUTCMinutes()).slice(-2) + ':' +
465
                ('0' + date.getUTCSeconds()).slice(-2);
466
        },
467
468
        _formatPercentage: function (floatValue) {
469
            return (floatValue * 100).toFixed(2) + ' %';
470
        },
471
472
        _renderExtendedProgress: function (data) {
473
            return this._formatBitrate(data.bitrate) + ' | ' +
474
                this._formatTime(
475
                    (data.total - data.loaded) * 8 / data.bitrate
476
                ) + ' | ' +
477
                this._formatPercentage(
478
                    data.loaded / data.total
479
                ) + ' | ' +
480
                this._formatFileSize(data.loaded) + ' / ' +
481
                this._formatFileSize(data.total);
482
        },
483
484
        _renderTemplate: function (func, files) {
485
            if (!func) {
486
                return $();
487
            }
488
            var result = func({
489
                files: files,
490
                formatFileSize: this._formatFileSize,
491
                options: this.options
492
            });
493
            if (result instanceof $) {
494
                return result;
495
            }
496
            return $(this.options.templatesContainer).html(result).children();
497
        },
498
499
        _renderPreviews: function (data) {
500
            data.context.find('.preview').each(function (index, elm) {
501
                $(elm).append(data.files[index].preview);
502
            });
503
        },
504
505
        _renderUpload: function (files) {
506
            return this._renderTemplate(
507
                this.options.uploadTemplate,
508
                files
509
            );
510
        },
511
512
        _renderDownload: function (files) {
513
            return this._renderTemplate(
514
                this.options.downloadTemplate,
515
                files
516
            ).find('a[download]').each(this._enableDragToDesktop).end();
517
        },
518
519
        _startHandler: function (e) {
520
            e.preventDefault();
521
            var button = $(e.currentTarget),
522
                template = button.closest('.template-upload'),
523
                data = template.data('data');
524
            button.prop('disabled', true);
525
            if (data && data.submit) {
526
                data.submit();
527
            }
528
        },
529
530
        _cancelHandler: function (e) {
531
            e.preventDefault();
532
            var template = $(e.currentTarget)
533
                    .closest('.template-upload,.template-download'),
534
                data = template.data('data') || {};
535
            data.context = data.context || template;
536
            if (data.abort) {
537
                data.abort();
538
            } else {
539
                data.errorThrown = 'abort';
540
                this._trigger('fail', e, data);
541
            }
542
        },
543
544
        _deleteHandler: function (e) {
545
            e.preventDefault();
546
            var button = $(e.currentTarget);
547
            this._trigger('destroy', e, $.extend({
548
                context: button.closest('.template-download'),
549
                type: 'DELETE'
550
            }, button.data()));
551
        },
552
553
        _forceReflow: function (node) {
554
            return $.support.transition && node.length &&
555
                node[0].offsetWidth;
556
        },
557
558
        _transition: function (node) {
559
            var dfd = $.Deferred();
560
            if ($.support.transition && node.hasClass('fade') && node.is(':visible')) {
561
                node.bind(
562
                    $.support.transition.end,
563
                    function (e) {
564
                        // Make sure we don't respond to other transitions events
565
                        // in the container element, e.g. from button elements:
566
                        if (e.target === node[0]) {
567
                            node.unbind($.support.transition.end);
568
                            dfd.resolveWith(node);
569
                        }
570
                    }
571
                ).toggleClass('in');
572
            } else {
573
                node.toggleClass('in');
574
                dfd.resolveWith(node);
575
            }
576
            return dfd;
577
        },
578
579
        _initButtonBarEventHandlers: function () {
580
            var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
581
                filesList = this.options.filesContainer;
582
            this._on(fileUploadButtonBar.find('.start'), {
583
                click: function (e) {
584
                    e.preventDefault();
585
                    filesList.find('.start').click();
586
                }
587
            });
588
            this._on(fileUploadButtonBar.find('.cancel'), {
589
                click: function (e) {
590
                    e.preventDefault();
591
                    filesList.find('.cancel').click();
592
                }
593
            });
594
            this._on(fileUploadButtonBar.find('.delete'), {
595
                click: function (e) {
596
                    e.preventDefault();
597
                    filesList.find('.toggle:checked')
598
                        .closest('.template-download')
599
                        .find('.delete').click();
600
                    fileUploadButtonBar.find('.toggle')
601
                        .prop('checked', false);
602
                }
603
            });
604
            this._on(fileUploadButtonBar.find('.toggle'), {
605
                change: function (e) {
606
                    filesList.find('.toggle').prop(
607
                        'checked',
608
                        $(e.currentTarget).is(':checked')
609
                    );
610
                }
611
            });
612
        },
613
614
        _destroyButtonBarEventHandlers: function () {
615
            this._off(
616
                this.element.find('.fileupload-buttonbar')
617
                    .find('.start, .cancel, .delete'),
618
                'click'
619
            );
620
            this._off(
621
                this.element.find('.fileupload-buttonbar .toggle'),
622
                'change.'
623
            );
624
        },
625
626
        _initEventHandlers: function () {
627
            this._super();
628
            this._on(this.options.filesContainer, {
629
                'click .start': this._startHandler,
630
                'click .cancel': this._cancelHandler,
631
                'click .delete': this._deleteHandler
632
            });
633
            this._initButtonBarEventHandlers();
634
        },
635
636
        _destroyEventHandlers: function () {
637
            this._destroyButtonBarEventHandlers();
638
            this._off(this.options.filesContainer, 'click');
639
            this._super();
640
        },
641
642
        _enableFileInputButton: function () {
643
            this.element.find('.fileinput-button input')
644
                .prop('disabled', false)
645
                .parent().removeClass('disabled');
646
        },
647
648
        _disableFileInputButton: function () {
649
            this.element.find('.fileinput-button input')
650
                .prop('disabled', true)
651
                .parent().addClass('disabled');
652
        },
653
654
        _initTemplates: function () {
655
            var options = this.options;
656
            options.templatesContainer = this.document[0].createElement(
657
                options.filesContainer.prop('nodeName')
658
            );
659
            if (tmpl) {
660
                if (options.uploadTemplateId) {
661
                    options.uploadTemplate = tmpl(options.uploadTemplateId);
662
                }
663
                if (options.downloadTemplateId) {
664
                    options.downloadTemplate = tmpl(options.downloadTemplateId);
665
                }
666
            }
667
        },
668
669
        _initFilesContainer: function () {
670
            var options = this.options;
671
            if (options.filesContainer === undefined) {
672
                options.filesContainer = this.element.find('.files');
673
            } else if (!(options.filesContainer instanceof $)) {
674
                options.filesContainer = $(options.filesContainer);
675
            }
676
        },
677
678
        _initSpecialOptions: function () {
679
            this._super();
680
            this._initFilesContainer();
681
            this._initTemplates();
682
        },
683
684
        _create: function () {
685
            this._super();
686
            this._resetFinishedDeferreds();
687
            if (!$.support.fileInput) {
688
                this._disableFileInputButton();
689
            }
690
        },
691
692
        enable: function () {
693
            var wasDisabled = false;
694
            if (this.options.disabled) {
695
                wasDisabled = true;
696
            }
697
            this._super();
698
            if (wasDisabled) {
699
                this.element.find('input, button').prop('disabled', false);
700
                this._enableFileInputButton();
701
            }
702
        },
703
704
        disable: function () {
705
            if (!this.options.disabled) {
706
                this.element.find('input, button').prop('disabled', true);
707
                this._disableFileInputButton();
708
            }
709
            this._super();
710
        }
711
712
    });
713
714
}));
715