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

Complexity

Total Complexity 100
Complexity/F 1.69

Size

Lines of Code 423
Function Count 59

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 6144
dl 0
loc 423
rs 3.12
c 0
b 0
f 0
wmc 100
mnd 3
bc 95
fnc 59
bpm 1.6101
cpm 1.6949
noi 1

1 Function

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

How to fix   Complexity   

Complexity

Complex classes like resources/lib/blueimp-file-upload/js/jquery.fileupload-angular.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 AngularJS Plugin
3
 * https://github.com/blueimp/jQuery-File-Upload
4
 *
5
 * Copyright 2013, 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, angular, require */
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
            'angular',
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('angular'),
32
            require('./jquery.fileupload-image'),
33
            require('./jquery.fileupload-audio'),
34
            require('./jquery.fileupload-video'),
35
            require('./jquery.fileupload-validate')
36
        );
37
    } else {
38
        factory();
39
    }
40
}(function () {
41
    'use strict';
42
43
    angular.module('blueimp.fileupload', [])
44
45
        // The fileUpload service provides configuration options
46
        // for the fileUpload directive and default handlers for
47
        // File Upload events:
48
        .provider('fileUpload', function () {
49
            var scopeEvalAsync = function (expression) {
50
                    var scope = angular.element(this)
51
                            .fileupload('option', 'scope');
52
                    // Schedule a new $digest cycle if not already inside of one
53
                    // and evaluate the given expression:
54
                    scope.$evalAsync(expression);
55
                },
56
                addFileMethods = function (scope, data) {
57
                    var files = data.files,
58
                        file = files[0];
59
                    angular.forEach(files, function (file, index) {
60
                        file._index = index;
61
                        file.$state = function () {
62
                            return data.state();
63
                        };
64
                        file.$processing = function () {
65
                            return data.processing();
66
                        };
67
                        file.$progress = function () {
68
                            return data.progress();
69
                        };
70
                        file.$response = function () {
71
                            return data.response();
72
                        };
73
                    });
74
                    file.$submit = function () {
75
                        if (!file.error) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !file.error 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...
76
                            return data.submit();
77
                        }
78
                    };
79
                    file.$cancel = function () {
80
                        return data.abort();
81
                    };
82
                },
83
                $config;
84
            $config = this.defaults = {
85
                handleResponse: function (e, data) {
86
                    var files = data.result && data.result.files;
87
                    if (files) {
88
                        data.scope.replace(data.files, files);
89
                    } else if (data.errorThrown ||
90
                            data.textStatus === 'error') {
91
                        data.files[0].error = data.errorThrown ||
92
                            data.textStatus;
93
                    }
94
                },
95
                add: function (e, data) {
96
                    if (e.isDefaultPrevented()) {
97
                        return false;
98
                    }
99
                    var scope = data.scope,
100
                        filesCopy = [];
101
                    angular.forEach(data.files, function (file) {
102
                        filesCopy.push(file);
103
                    });
104
                    scope.$parent.$applyAsync(function () {
105
                        addFileMethods(scope, data);
106
                        var method = scope.option('prependFiles') ?
107
                                'unshift' : 'push';
108
                        Array.prototype[method].apply(scope.queue, data.files);
109
                    });
110
                    data.process(function () {
111
                        return scope.process(data);
112
                    }).always(function () {
113
                        scope.$parent.$applyAsync(function () {
114
                            addFileMethods(scope, data);
115
                            scope.replace(filesCopy, data.files);
116
                        });
117
                    }).then(function () {
118
                        if ((scope.option('autoUpload') ||
119
                                data.autoUpload) &&
120
                                data.autoUpload !== false) {
121
                            data.submit();
122
                        }
123
                    });
124
                },
125
                done: function (e, data) {
126
                    if (e.isDefaultPrevented()) {
127
                        return false;
128
                    }
129
                    var that = this;
130
                    data.scope.$apply(function () {
131
                        data.handleResponse.call(that, e, data);
132
                    });
133
                },
134
                fail: function (e, data) {
135
                    if (e.isDefaultPrevented()) {
136
                        return false;
137
                    }
138
                    var that = this,
139
                        scope = data.scope;
140
                    if (data.errorThrown === 'abort') {
141
                        scope.clear(data.files);
142
                        return;
143
                    }
144
                    scope.$apply(function () {
145
                        data.handleResponse.call(that, e, data);
146
                    });
147
                },
148
                stop: scopeEvalAsync,
149
                processstart: scopeEvalAsync,
150
                processstop: scopeEvalAsync,
151
                getNumberOfFiles: function () {
152
                    var scope = this.scope;
153
                    return scope.queue.length - scope.processing();
154
                },
155
                dataType: 'json',
156
                autoUpload: false
157
            };
158
            this.$get = [
159
                function () {
160
                    return {
161
                        defaults: $config
162
                    };
163
                }
164
            ];
165
        })
166
167
        // Format byte numbers to readable presentations:
168
        .provider('formatFileSizeFilter', function () {
169
            var $config = {
170
                // Byte units following the IEC format
171
                // http://en.wikipedia.org/wiki/Kilobyte
172
                units: [
173
                    {size: 1000000000, suffix: ' GB'},
174
                    {size: 1000000, suffix: ' MB'},
175
                    {size: 1000, suffix: ' KB'}
176
                ]
177
            };
178
            this.defaults = $config;
179
            this.$get = function () {
180
                return function (bytes) {
181
                    if (!angular.isNumber(bytes)) {
182
                        return '';
183
                    }
184
                    var unit = true,
185
                        i = 0,
186
                        prefix,
187
                        suffix;
188
                    while (unit) {
189
                        unit = $config.units[i];
190
                        prefix = unit.prefix || '';
191
                        suffix = unit.suffix || '';
192
                        if (i === $config.units.length - 1 || bytes >= unit.size) {
193
                            return prefix + (bytes / unit.size).toFixed(2) + suffix;
194
                        }
195
                        i += 1;
196
                    }
197
                };
198
            };
199
        })
200
201
        // The FileUploadController initializes the fileupload widget and
202
        // provides scope methods to control the File Upload functionality:
203
        .controller('FileUploadController', [
204
            '$scope', '$element', '$attrs', '$window', 'fileUpload','$q',
205
            function ($scope, $element, $attrs, $window, fileUpload, $q) {
206
                var uploadMethods = {
207
                    progress: function () {
208
                        return $element.fileupload('progress');
209
                    },
210
                    active: function () {
211
                        return $element.fileupload('active');
212
                    },
213
                    option: function (option, data) {
214
                        if (arguments.length === 1) {
215
                            return $element.fileupload('option', option);
216
                        }
217
                        $element.fileupload('option', option, data);
218
                    },
219
                    add: function (data) {
220
                        return $element.fileupload('add', data);
221
                    },
222
                    send: function (data) {
223
                        return $element.fileupload('send', data);
224
                    },
225
                    process: function (data) {
226
                        return $element.fileupload('process', data);
227
                    },
228
                    processing: function (data) {
229
                        return $element.fileupload('processing', data);
230
                    }
231
                };
232
                $scope.disabled = !$window.jQuery.support.fileInput;
233
                $scope.queue = $scope.queue || [];
234
                $scope.clear = function (files) {
235
                    var queue = this.queue,
236
                        i = queue.length,
237
                        file = files,
238
                        length = 1;
239
                    if (angular.isArray(files)) {
240
                        file = files[0];
241
                        length = files.length;
242
                    }
243
                    while (i) {
244
                        i -= 1;
245
                        if (queue[i] === file) {
246
                            return queue.splice(i, length);
247
                        }
248
                    }
249
                };
250
                $scope.replace = function (oldFiles, newFiles) {
251
                    var queue = this.queue,
252
                        file = oldFiles[0],
253
                        i,
254
                        j;
255
                    for (i = 0; i < queue.length; i += 1) {
256
                        if (queue[i] === file) {
257
                            for (j = 0; j < newFiles.length; j += 1) {
258
                                queue[i + j] = newFiles[j];
259
                            }
260
                            return;
261
                        }
262
                    }
263
                };
264
                $scope.applyOnQueue = function (method) {
265
                    var list = this.queue.slice(0),
266
                        i,
267
                        file,
268
                        promises = [];
269
                    for (i = 0; i < list.length; i += 1) {
270
                        file = list[i];
271
                        if (file[method]) {
272
                            promises.push(file[method]());
273
                        }
274
                    }
275
                    return $q.all(promises);
276
                };
277
                $scope.submit = function () {
278
                    return this.applyOnQueue('$submit');
279
                };
280
                $scope.cancel = function () {
281
                    return this.applyOnQueue('$cancel');
282
                };
283
                // Add upload methods to the scope:
284
                angular.extend($scope, uploadMethods);
285
                // The fileupload widget will initialize with
286
                // the options provided via "data-"-parameters,
287
                // as well as those given via options object:
288
                $element.fileupload(angular.extend(
289
                    {scope: $scope},
290
                    fileUpload.defaults
291
                )).on('fileuploadadd', function (e, data) {
292
                    data.scope = $scope;
293
                }).on('fileuploadfail', function (e, data) {
294
                    if (data.errorThrown === 'abort') {
295
                        return;
296
                    }
297
                    if (data.dataType &&
298
                            data.dataType.indexOf('json') === data.dataType.length - 4) {
299
                        try {
300
                            data.result = angular.fromJson(data.jqXHR.responseText);
301
                        } catch (ignore) {}
302
                    }
303
                }).on([
304
                    'fileuploadadd',
305
                    'fileuploadsubmit',
306
                    'fileuploadsend',
307
                    'fileuploaddone',
308
                    'fileuploadfail',
309
                    'fileuploadalways',
310
                    'fileuploadprogress',
311
                    'fileuploadprogressall',
312
                    'fileuploadstart',
313
                    'fileuploadstop',
314
                    'fileuploadchange',
315
                    'fileuploadpaste',
316
                    'fileuploaddrop',
317
                    'fileuploaddragover',
318
                    'fileuploadchunksend',
319
                    'fileuploadchunkdone',
320
                    'fileuploadchunkfail',
321
                    'fileuploadchunkalways',
322
                    'fileuploadprocessstart',
323
                    'fileuploadprocess',
324
                    'fileuploadprocessdone',
325
                    'fileuploadprocessfail',
326
                    'fileuploadprocessalways',
327
                    'fileuploadprocessstop'
328
                ].join(' '), function (e, data) {
329
                    $scope.$parent.$applyAsync(function () {
330
                        if ($scope.$emit(e.type, data).defaultPrevented) {
331
                            e.preventDefault();
332
                        }
333
                    });
334
                }).on('remove', function () {
335
                    // Remove upload methods from the scope,
336
                    // when the widget is removed:
337
                    var method;
338
                    for (method in uploadMethods) {
339
                        if (uploadMethods.hasOwnProperty(method)) {
340
                            delete $scope[method];
341
                        }
342
                    }
343
                });
344
                // Observe option changes:
345
                $scope.$watch(
346
                    $attrs.fileUpload,
347
                    function (newOptions) {
348
                        if (newOptions) {
349
                            $element.fileupload('option', newOptions);
350
                        }
351
                    }
352
                );
353
            }
354
        ])
355
356
        // Provide File Upload progress feedback:
357
        .controller('FileUploadProgressController', [
358
            '$scope', '$attrs', '$parse',
359
            function ($scope, $attrs, $parse) {
360
                var fn = $parse($attrs.fileUploadProgress),
361
                    update = function () {
362
                        var progress = fn($scope);
363
                        if (!progress || !progress.total) {
364
                            return;
365
                        }
366
                        $scope.num = Math.floor(
367
                            progress.loaded / progress.total * 100
368
                        );
369
                    };
370
                update();
371
                $scope.$watch(
372
                    $attrs.fileUploadProgress + '.loaded',
373
                    function (newValue, oldValue) {
374
                        if (newValue !== oldValue) {
375
                            update();
376
                        }
377
                    }
378
                );
379
            }
380
        ])
381
382
        // Display File Upload previews:
383
        .controller('FileUploadPreviewController', [
384
            '$scope', '$element', '$attrs',
385
            function ($scope, $element, $attrs) {
386
                $scope.$watch(
387
                    $attrs.fileUploadPreview + '.preview',
388
                    function (preview) {
389
                        $element.empty();
390
                        if (preview) {
391
                            $element.append(preview);
392
                        }
393
                    }
394
                );
395
            }
396
        ])
397
398
        .directive('fileUpload', function () {
399
            return {
400
                controller: 'FileUploadController',
401
                scope: true
402
            };
403
        })
404
405
        .directive('fileUploadProgress', function () {
406
            return {
407
                controller: 'FileUploadProgressController',
408
                scope: true
409
            };
410
        })
411
412
        .directive('fileUploadPreview', function () {
413
            return {
414
                controller: 'FileUploadPreviewController'
415
            };
416
        })
417
418
        // Enhance the HTML5 download attribute to
419
        // allow drag&drop of files to the desktop:
420
        .directive('download', function () {
421
            return function (scope, elm) {
422
                elm.on('dragstart', function (e) {
423
                    try {
424
                        e.originalEvent.dataTransfer.setData(
425
                            'DownloadURL',
426
                            [
427
                                'application/octet-stream',
428
                                elm.prop('download'),
429
                                elm.prop('href')
430
                            ].join(':')
431
                        );
432
                    } catch (ignore) {}
433
                });
434
            };
435
        });
436
437
}));
438