angularjs-file-upload-rails 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eebaf468ac5a74e0da00f5db6a8d40267a39b712
4
+ data.tar.gz: a700b2c4e863e97ef119d064cba43fc64933b68f
5
+ SHA512:
6
+ metadata.gz: aaa843b398eae0f2d35372b1c8a8a15ce5b2f9939a05e61d11ff85bb505f47a820a7c57b695f46ab5fe4bde28183e581d3e2689410d81aab39dd73725b606a41
7
+ data.tar.gz: 4ff131e06d45a8d509442e3a39ab03dd74048d02560bab2aea4d6763b06be9d331bf5458ab521741a548d169229cd9a2f4e0d12dd88a28557b516ef5f34b1847
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in angularjs-file-upload.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Marthyn Olthof
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # Angularjs::File::Upload
2
+
3
+ A gem that uses [angular-file-upload](https://github.com/nervgh/angular-file-upload) as an asset in the Rails Asset Pipeline.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'angularjs-file-upload', '~> 1.0.1'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```
22
+ $ gem install angularjs-file-upload
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Include it in your JavaScript manifest (e.g. `application.js`)
28
+
29
+ ```javascript
30
+ //= require angularjs-file-upload
31
+ ```
32
+ \* *be sure that angular is required before angularjs-file-upload*
33
+
34
+ ## Contributing
35
+
36
+ 1. Fork it
37
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
38
+ 3. Commit your changes (`git commit -m 'Add some feature'`)
39
+ 4. Push to the branch (`git push origin my-new-feature`)
40
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ require File.expand_path('../lib/angularjs-file-upload/version', __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "angularjs-file-upload-rails"
6
+ spec.version = AngularjsFileUpload::VERSION
7
+ spec.authors = ["Marthyn Olthof"]
8
+ spec.email = ["Marthyn@live.nl"]
9
+ spec.description = %q{Angular File Upload is a module for the AngularJS framework.}
10
+ spec.summary = %q{It supports drag-n-drop upload, upload progress, validation filters and a file upload queue.
11
+ It supports native HTML5 uploads, but degrades to a legacy iframe upload method for older browsers.
12
+ Works with any server side platform which supports standard HTML form uploads.}
13
+ spec.homepage = "https://github.com/marthyn/angularjs-file-upload-rails"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.3"
20
+ spec.add_development_dependency "rails", "~> 3.1"
21
+ spec.add_development_dependency "rake"
22
+ end
@@ -0,0 +1,1289 @@
1
+ /*
2
+ angular-file-upload v1.0.1
3
+ https://github.com/nervgh/angular-file-upload
4
+ */
5
+ (function(angular, factory) {
6
+ if (typeof define === 'function' && define.amd) {
7
+ define('angular-file-upload', ['angular'], function(angular) {
8
+ return factory(angular);
9
+ });
10
+ } else {
11
+ return factory(angular);
12
+ }
13
+ }(typeof angular === 'undefined' ? null : angular || null, function(angular) {
14
+
15
+ var module = angular.module('angularFileUpload', []);
16
+ 'use strict';
17
+
18
+ /**
19
+ * Classes
20
+ *
21
+ * FileUploader
22
+ * FileUploader.FileLikeObject
23
+ * FileUploader.FileItem
24
+ * FileUploader.FileDirective
25
+ * FileUploader.FileSelect
26
+ * FileUploader.FileDrop
27
+ * FileUploader.FileOver
28
+ */
29
+
30
+ module
31
+
32
+
33
+ .value('fileUploaderOptions', {
34
+ url: '/',
35
+ alias: 'file',
36
+ headers: {},
37
+ queue: [],
38
+ progress: 0,
39
+ autoUpload: false,
40
+ removeAfterUpload: false,
41
+ method: 'POST',
42
+ filters: [],
43
+ formData: [],
44
+ queueLimit: Number.MAX_VALUE,
45
+ withCredentials: false
46
+ })
47
+
48
+
49
+ .factory('FileUploader', ['fileUploaderOptions', '$rootScope', '$http', '$window', '$compile',
50
+ function(fileUploaderOptions, $rootScope, $http, $window, $compile) {
51
+ /**
52
+ * Creates an instance of FileUploader
53
+ * @param {Object} [options]
54
+ * @constructor
55
+ */
56
+ function FileUploader(options) {
57
+ var settings = angular.copy(fileUploaderOptions);
58
+ angular.extend(this, settings, options, {
59
+ isUploading: false,
60
+ _nextIndex: 0,
61
+ _failFilterIndex: -1,
62
+ _directives: {select: [], drop: [], over: []}
63
+ });
64
+
65
+ // add default filters
66
+ this.filters.unshift({name: 'queueLimit', fn: this._queueLimitFilter});
67
+ this.filters.unshift({name: 'folder', fn: this._folderFilter});
68
+ }
69
+ /**********************
70
+ * PUBLIC
71
+ **********************/
72
+ /**
73
+ * Checks a support the html5 uploader
74
+ * @returns {Boolean}
75
+ * @readonly
76
+ */
77
+ FileUploader.prototype.isHTML5 = !!($window.File && $window.FormData);
78
+ /**
79
+ * Adds items to the queue
80
+ * @param {FileList|File|HTMLInputElement} files
81
+ * @param {Object} [options]
82
+ * @param {Array<Function>|String} filters
83
+ */
84
+ FileUploader.prototype.addToQueue = function(files, options, filters) {
85
+ var list = angular.isElement(files) ? [files]: files;
86
+ var arrayOfFilters = this._getFilters(filters);
87
+ var count = this.queue.length;
88
+ var addedFileItems = [];
89
+
90
+ angular.forEach(list, function(file) {
91
+ var item = this._getFileOrFileLikeObject(file);
92
+
93
+ if (this._isValidFile(item, arrayOfFilters, options)) {
94
+ var input = this.isFile(item) ? null : file;
95
+ var fileItem = new FileUploader.FileItem(this, item, options, input);
96
+ addedFileItems.push(fileItem);
97
+ this.queue.push(fileItem);
98
+ this._onAfterAddingFile(fileItem);
99
+ } else {
100
+ var filter = this.filters[this._failFilterIndex];
101
+ this._onWhenAddingFileFailed(item, filter, options);
102
+ }
103
+ }, this);
104
+
105
+ if(this.queue.length !== count) {
106
+ this._onAfterAddingAll(addedFileItems);
107
+ this.progress = this._getTotalProgress();
108
+ }
109
+
110
+ this._render();
111
+ if (this.autoUpload) this.uploadAll();
112
+ };
113
+ /**
114
+ * Remove items from the queue. Remove last: index = -1
115
+ * @param {FileItem|Number} value
116
+ */
117
+ FileUploader.prototype.removeFromQueue = function(value) {
118
+ var index = this.getIndexOfItem(value);
119
+ var item = this.queue[index];
120
+ if (item.isUploading) item.cancel();
121
+ this.queue.splice(index, 1);
122
+ item._destroy();
123
+ this.progress = this._getTotalProgress();
124
+ };
125
+ /**
126
+ * Clears the queue
127
+ */
128
+ FileUploader.prototype.clearQueue = function() {
129
+ while(this.queue.length) {
130
+ this.queue[0].remove();
131
+ }
132
+ this.progress = 0;
133
+ };
134
+ /**
135
+ * Uploads a item from the queue
136
+ * @param {FileItem|Number} value
137
+ */
138
+ FileUploader.prototype.uploadItem = function(value) {
139
+ var index = this.getIndexOfItem(value);
140
+ var item = this.queue[index];
141
+ var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport';
142
+
143
+ item._prepareToUploading();
144
+ if(this.isUploading) return;
145
+
146
+ this.isUploading = true;
147
+ this[transport](item);
148
+ };
149
+ /**
150
+ * Cancels uploading of item from the queue
151
+ * @param {FileItem|Number} value
152
+ */
153
+ FileUploader.prototype.cancelItem = function(value) {
154
+ var index = this.getIndexOfItem(value);
155
+ var item = this.queue[index];
156
+ var prop = this.isHTML5 ? '_xhr' : '_form';
157
+ if (item && item.isUploading) item[prop].abort();
158
+ };
159
+ /**
160
+ * Uploads all not uploaded items of queue
161
+ */
162
+ FileUploader.prototype.uploadAll = function() {
163
+ var items = this.getNotUploadedItems().filter(function(item) {
164
+ return !item.isUploading;
165
+ });
166
+ if (!items.length) return;
167
+
168
+ angular.forEach(items, function(item) {
169
+ item._prepareToUploading();
170
+ });
171
+ items[0].upload();
172
+ };
173
+ /**
174
+ * Cancels all uploads
175
+ */
176
+ FileUploader.prototype.cancelAll = function() {
177
+ var items = this.getNotUploadedItems();
178
+ angular.forEach(items, function(item) {
179
+ item.cancel();
180
+ });
181
+ };
182
+ /**
183
+ * Returns "true" if value an instance of File
184
+ * @param {*} value
185
+ * @returns {Boolean}
186
+ * @private
187
+ */
188
+ FileUploader.prototype.isFile = function(value) {
189
+ var fn = $window.File;
190
+ return (fn && value instanceof fn);
191
+ };
192
+ /**
193
+ * Returns "true" if value an instance of FileLikeObject
194
+ * @param {*} value
195
+ * @returns {Boolean}
196
+ * @private
197
+ */
198
+ FileUploader.prototype.isFileLikeObject = function(value) {
199
+ return value instanceof FileUploader.FileLikeObject;
200
+ };
201
+ /**
202
+ * Returns a index of item from the queue
203
+ * @param {Item|Number} value
204
+ * @returns {Number}
205
+ */
206
+ FileUploader.prototype.getIndexOfItem = function(value) {
207
+ return angular.isNumber(value) ? value : this.queue.indexOf(value);
208
+ };
209
+ /**
210
+ * Returns not uploaded items
211
+ * @returns {Array}
212
+ */
213
+ FileUploader.prototype.getNotUploadedItems = function() {
214
+ return this.queue.filter(function(item) {
215
+ return !item.isUploaded;
216
+ });
217
+ };
218
+ /**
219
+ * Returns items ready for upload
220
+ * @returns {Array}
221
+ */
222
+ FileUploader.prototype.getReadyItems = function() {
223
+ return this.queue
224
+ .filter(function(item) {
225
+ return (item.isReady && !item.isUploading);
226
+ })
227
+ .sort(function(item1, item2) {
228
+ return item1.index - item2.index;
229
+ });
230
+ };
231
+ /**
232
+ * Destroys instance of FileUploader
233
+ */
234
+ FileUploader.prototype.destroy = function() {
235
+ angular.forEach(this._directives, function(key) {
236
+ angular.forEach(this._directives[key], function(object) {
237
+ object.destroy();
238
+ }, this);
239
+ }, this);
240
+ };
241
+ /**
242
+ * Callback
243
+ * @param {Array} fileItems
244
+ */
245
+ FileUploader.prototype.onAfterAddingAll = function(fileItems) {};
246
+ /**
247
+ * Callback
248
+ * @param {FileItem} fileItem
249
+ */
250
+ FileUploader.prototype.onAfterAddingFile = function(fileItem) {};
251
+ /**
252
+ * Callback
253
+ * @param {File|Object} item
254
+ * @param {Object} filter
255
+ * @param {Object} options
256
+ * @private
257
+ */
258
+ FileUploader.prototype.onWhenAddingFileFailed = function(item, filter, options) {};
259
+ /**
260
+ * Callback
261
+ * @param {FileItem} fileItem
262
+ */
263
+ FileUploader.prototype.onBeforeUploadItem = function(fileItem) {};
264
+ /**
265
+ * Callback
266
+ * @param {FileItem} fileItem
267
+ * @param {Number} progress
268
+ */
269
+ FileUploader.prototype.onProgressItem = function(fileItem, progress) {};
270
+ /**
271
+ * Callback
272
+ * @param {Number} progress
273
+ */
274
+ FileUploader.prototype.onProgressAll = function(progress) {};
275
+ /**
276
+ * Callback
277
+ * @param {FileItem} item
278
+ * @param {*} response
279
+ * @param {Number} status
280
+ * @param {Object} headers
281
+ */
282
+ FileUploader.prototype.onSuccessItem = function(item, response, status, headers) {};
283
+ /**
284
+ * Callback
285
+ * @param {FileItem} item
286
+ * @param {*} response
287
+ * @param {Number} status
288
+ * @param {Object} headers
289
+ */
290
+ FileUploader.prototype.onErrorItem = function(item, response, status, headers) {};
291
+ /**
292
+ * Callback
293
+ * @param {FileItem} item
294
+ * @param {*} response
295
+ * @param {Number} status
296
+ * @param {Object} headers
297
+ */
298
+ FileUploader.prototype.onCancelItem = function(item, response, status, headers) {};
299
+ /**
300
+ * Callback
301
+ * @param {FileItem} item
302
+ * @param {*} response
303
+ * @param {Number} status
304
+ * @param {Object} headers
305
+ */
306
+ FileUploader.prototype.onCompleteItem = function(item, response, status, headers) {};
307
+ /**
308
+ * Callback
309
+ */
310
+ FileUploader.prototype.onCompleteAll = function() {};
311
+ /**********************
312
+ * PRIVATE
313
+ **********************/
314
+ /**
315
+ * Returns the total progress
316
+ * @param {Number} [value]
317
+ * @returns {Number}
318
+ * @private
319
+ */
320
+ FileUploader.prototype._getTotalProgress = function(value) {
321
+ if(this.removeAfterUpload) return value || 0;
322
+
323
+ var notUploaded = this.getNotUploadedItems().length;
324
+ var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length;
325
+ var ratio = 100 / this.queue.length;
326
+ var current = (value || 0) * ratio / 100;
327
+
328
+ return Math.round(uploaded * ratio + current);
329
+ };
330
+ /**
331
+ * Returns array of filters
332
+ * @param {Array<Function>|String} filters
333
+ * @returns {Array<Function>}
334
+ * @private
335
+ */
336
+ FileUploader.prototype._getFilters = function(filters) {
337
+ if (angular.isUndefined(filters)) return this.filters;
338
+ if (angular.isArray(filters)) return filters;
339
+ var names = filters.split(/\s*,/);
340
+ return this.filters.filter(function(filter) {
341
+ return names.indexOf(filter.name) !== -1;
342
+ }, this);
343
+ };
344
+ /**
345
+ * Updates html
346
+ * @private
347
+ */
348
+ FileUploader.prototype._render = function() {
349
+ if (!$rootScope.$$phase) $rootScope.$apply();
350
+ };
351
+ /**
352
+ * Returns "true" if item is a file (not folder)
353
+ * @param {File|FileLikeObject} item
354
+ * @returns {Boolean}
355
+ * @private
356
+ */
357
+ FileUploader.prototype._folderFilter = function(item) {
358
+ return !!(item.size || item.type);
359
+ };
360
+ /**
361
+ * Returns "true" if the limit has not been reached
362
+ * @returns {Boolean}
363
+ * @private
364
+ */
365
+ FileUploader.prototype._queueLimitFilter = function() {
366
+ return this.queue.length < this.queueLimit;
367
+ };
368
+ /**
369
+ * Returns "true" if file pass all filters
370
+ * @param {File|Object} file
371
+ * @param {Array<Function>} filters
372
+ * @param {Object} options
373
+ * @returns {Boolean}
374
+ * @private
375
+ */
376
+ FileUploader.prototype._isValidFile = function(file, filters, options) {
377
+ this._failFilterIndex = -1;
378
+ return !filters.length ? true : filters.every(function(filter) {
379
+ this._failFilterIndex++;
380
+ return filter.fn.call(this, file, options);
381
+ }, this);
382
+ };
383
+ /**
384
+ * Returns a file or a file-like object
385
+ * @param {File|HTMLInputElement} some
386
+ * @returns {File|Object}
387
+ * @private
388
+ */
389
+ FileUploader.prototype._getFileOrFileLikeObject = function(some) {
390
+ if (this.isFile(some) || this.isFileLikeObject(some)) return some;
391
+ return new FileUploader.FileLikeObject(some.value);
392
+ };
393
+ /**
394
+ * Checks whether upload successful
395
+ * @param {Number} status
396
+ * @returns {Boolean}
397
+ * @private
398
+ */
399
+ FileUploader.prototype._isSuccessCode = function(status) {
400
+ return (status >= 200 && status < 300) || status === 304;
401
+ };
402
+ /**
403
+ * Transforms the server response
404
+ * @param {*} response
405
+ * @returns {*}
406
+ * @private
407
+ */
408
+ FileUploader.prototype._transformResponse = function(response) {
409
+ angular.forEach($http.defaults.transformResponse, function(transformFn) {
410
+ response = transformFn(response);
411
+ });
412
+ return response;
413
+ };
414
+ /**
415
+ * Parsed response headers
416
+ * @param headers
417
+ * @returns {Object}
418
+ * @see https://github.com/angular/angular.js/blob/master/src/ng/http.js
419
+ * @private
420
+ */
421
+ FileUploader.prototype._parseHeaders = function(headers) {
422
+ var parsed = {}, key, val, i;
423
+
424
+ if (!headers) return parsed;
425
+
426
+ function trim(string) {
427
+ return string.replace(/^\s+/, '').replace(/\s+$/, '');
428
+ }
429
+ function lowercase(string) {
430
+ return string.toLowerCase();
431
+ }
432
+
433
+ angular.forEach(headers.split('\n'), function(line) {
434
+ i = line.indexOf(':');
435
+ key = lowercase(trim(line.substr(0, i)));
436
+ val = trim(line.substr(i + 1));
437
+
438
+ if (key) {
439
+ parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
440
+ }
441
+ });
442
+
443
+ return parsed;
444
+ };
445
+ /**
446
+ * The XMLHttpRequest transport
447
+ * @param {FileItem} item
448
+ * @private
449
+ */
450
+ FileUploader.prototype._xhrTransport = function(item) {
451
+ var xhr = item._xhr = new XMLHttpRequest();
452
+ var form = new FormData();
453
+ var that = this;
454
+
455
+ that._onBeforeUploadItem(item);
456
+
457
+ angular.forEach(item.formData, function(obj) {
458
+ angular.forEach(obj, function(value, key) {
459
+ form.append(key, value);
460
+ });
461
+ });
462
+
463
+ form.append(item.alias, item._file);
464
+
465
+ xhr.upload.onprogress = function(event) {
466
+ var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
467
+ that._onProgressItem(item, progress);
468
+ };
469
+
470
+ xhr.onload = function() {
471
+ var headers = that._parseHeaders(xhr.getAllResponseHeaders());
472
+ var response = that._transformResponse(xhr.response);
473
+ var gist = that._isSuccessCode(xhr.status) ? 'Success' : 'Error';
474
+ var method = '_on' + gist + 'Item';
475
+ that[method](item, response, xhr.status, headers);
476
+ that._onCompleteItem(item, response, xhr.status, headers);
477
+ };
478
+
479
+ xhr.onerror = function() {
480
+ var headers = that._parseHeaders(xhr.getAllResponseHeaders());
481
+ var response = that._transformResponse(xhr.response);
482
+ that._onErrorItem(item, response, xhr.status, headers);
483
+ that._onCompleteItem(item, response, xhr.status, headers);
484
+ };
485
+
486
+ xhr.onabort = function() {
487
+ var headers = that._parseHeaders(xhr.getAllResponseHeaders());
488
+ var response = that._transformResponse(xhr.response);
489
+ that._onCancelItem(item, response, xhr.status, headers);
490
+ that._onCompleteItem(item, response, xhr.status, headers);
491
+ };
492
+
493
+ xhr.open(item.method, item.url, true);
494
+
495
+ xhr.withCredentials = item.withCredentials;
496
+
497
+ angular.forEach(item.headers, function(value, name) {
498
+ xhr.setRequestHeader(name, value);
499
+ });
500
+
501
+ xhr.send(form);
502
+ this._render();
503
+ };
504
+ /**
505
+ * The IFrame transport
506
+ * @param {FileItem} item
507
+ * @private
508
+ */
509
+ FileUploader.prototype._iframeTransport = function(item) {
510
+ var form = angular.element('<form style="display: none;" />');
511
+ var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
512
+ var input = item._input;
513
+ var that = this;
514
+
515
+ if (item._form) item._form.replaceWith(input); // remove old form
516
+ item._form = form; // save link to new form
517
+
518
+ that._onBeforeUploadItem(item);
519
+
520
+ input.prop('name', item.alias);
521
+
522
+ angular.forEach(item.formData, function(obj) {
523
+ angular.forEach(obj, function(value, key) {
524
+ form.append(angular.element('<input type="hidden" name="' + key + '" value="' + value + '" />'));
525
+ });
526
+ });
527
+
528
+ form.prop({
529
+ action: item.url,
530
+ method: 'POST',
531
+ target: iframe.prop('name'),
532
+ enctype: 'multipart/form-data',
533
+ encoding: 'multipart/form-data' // old IE
534
+ });
535
+
536
+ iframe.bind('load', function() {
537
+ try {
538
+ // Fix for legacy IE browsers that loads internal error page
539
+ // when failed WS response received. In consequence iframe
540
+ // content access denied error is thrown becouse trying to
541
+ // access cross domain page. When such thing occurs notifying
542
+ // with empty response object. See more info at:
543
+ // http://stackoverflow.com/questions/151362/access-is-denied-error-on-accessing-iframe-document-object
544
+ // Note that if non standard 4xx or 5xx error code returned
545
+ // from WS then response content can be accessed without error
546
+ // but 'XHR' status becomes 200. In order to avoid confusion
547
+ // returning response via same 'success' event handler.
548
+
549
+ // fixed angular.contents() for iframes
550
+ var html = iframe[0].contentDocument.body.innerHTML;
551
+ } catch (e) {}
552
+
553
+ var xhr = {response: html, status: 200, dummy: true};
554
+ var response = that._transformResponse(xhr.response);
555
+ var headers = {};
556
+
557
+ that._onSuccessItem(item, response, xhr.status, headers);
558
+ that._onCompleteItem(item, response, xhr.status, headers);
559
+ });
560
+
561
+ form.abort = function() {
562
+ var xhr = {status: 0, dummy: true};
563
+ var headers = {};
564
+ var response;
565
+
566
+ iframe.unbind('load').prop('src', 'javascript:false;');
567
+ form.replaceWith(input);
568
+
569
+ that._onCancelItem(item, response, xhr.status, headers);
570
+ that._onCompleteItem(item, response, xhr.status, headers);
571
+ };
572
+
573
+ input.after(form);
574
+ form.append(input).append(iframe);
575
+
576
+ form[0].submit();
577
+ this._render();
578
+ };
579
+ /**
580
+ * Inner callback
581
+ * @param {File|Object} item
582
+ * @param {Object} filter
583
+ * @param {Object} options
584
+ * @private
585
+ */
586
+ FileUploader.prototype._onWhenAddingFileFailed = function(item, filter, options) {
587
+ this.onWhenAddingFileFailed(item, filter, options);
588
+ };
589
+ /**
590
+ * Inner callback
591
+ * @param {FileItem} item
592
+ */
593
+ FileUploader.prototype._onAfterAddingFile = function(item) {
594
+ this.onAfterAddingFile(item);
595
+ };
596
+ /**
597
+ * Inner callback
598
+ * @param {Array<FileItem>} items
599
+ */
600
+ FileUploader.prototype._onAfterAddingAll = function(items) {
601
+ this.onAfterAddingAll(items);
602
+ };
603
+ /**
604
+ * Inner callback
605
+ * @param {FileItem} item
606
+ * @private
607
+ */
608
+ FileUploader.prototype._onBeforeUploadItem = function(item) {
609
+ item._onBeforeUpload();
610
+ this.onBeforeUploadItem(item);
611
+ };
612
+ /**
613
+ * Inner callback
614
+ * @param {FileItem} item
615
+ * @param {Number} progress
616
+ * @private
617
+ */
618
+ FileUploader.prototype._onProgressItem = function(item, progress) {
619
+ var total = this._getTotalProgress(progress);
620
+ this.progress = total;
621
+ item._onProgress(progress);
622
+ this.onProgressItem(item, progress);
623
+ this.onProgressAll(total);
624
+ this._render();
625
+ };
626
+ /**
627
+ * Inner callback
628
+ * @param {FileItem} item
629
+ * @param {*} response
630
+ * @param {Number} status
631
+ * @param {Object} headers
632
+ * @private
633
+ */
634
+ FileUploader.prototype._onSuccessItem = function(item, response, status, headers) {
635
+ item._onSuccess(response, status, headers);
636
+ this.onSuccessItem(item, response, status, headers);
637
+ };
638
+ /**
639
+ * Inner callback
640
+ * @param {FileItem} item
641
+ * @param {*} response
642
+ * @param {Number} status
643
+ * @param {Object} headers
644
+ * @private
645
+ */
646
+ FileUploader.prototype._onErrorItem = function(item, response, status, headers) {
647
+ item._onError(response, status, headers);
648
+ this.onErrorItem(item, response, status, headers);
649
+ };
650
+ /**
651
+ * Inner callback
652
+ * @param {FileItem} item
653
+ * @param {*} response
654
+ * @param {Number} status
655
+ * @param {Object} headers
656
+ * @private
657
+ */
658
+ FileUploader.prototype._onCancelItem = function(item, response, status, headers) {
659
+ item._onCancel(response, status, headers);
660
+ this.onCancelItem(item, response, status, headers);
661
+ };
662
+ /**
663
+ * Inner callback
664
+ * @param {FileItem} item
665
+ * @param {*} response
666
+ * @param {Number} status
667
+ * @param {Object} headers
668
+ * @private
669
+ */
670
+ FileUploader.prototype._onCompleteItem = function(item, response, status, headers) {
671
+ item._onComplete(response, status, headers);
672
+ this.onCompleteItem(item, response, status, headers);
673
+
674
+ var nextItem = this.getReadyItems()[0];
675
+ this.isUploading = false;
676
+
677
+ if(angular.isDefined(nextItem)) {
678
+ nextItem.upload();
679
+ return;
680
+ }
681
+
682
+ this.onCompleteAll();
683
+ this.progress = this._getTotalProgress();
684
+ this._render();
685
+ };
686
+ /**********************
687
+ * STATIC
688
+ **********************/
689
+ /**
690
+ * @borrows FileUploader.prototype.isFile
691
+ */
692
+ FileUploader.isFile = FileUploader.prototype.isFile;
693
+ /**
694
+ * @borrows FileUploader.prototype.isFileLikeObject
695
+ */
696
+ FileUploader.isFileLikeObject = FileUploader.prototype.isFileLikeObject;
697
+ /**
698
+ * @borrows FileUploader.prototype.isHTML5
699
+ */
700
+ FileUploader.isHTML5 = FileUploader.prototype.isHTML5;
701
+ /**
702
+ * Inherits a target (Class_1) by a source (Class_2)
703
+ * @param {Function} target
704
+ * @param {Function} source
705
+ */
706
+ FileUploader.inherit = function(target, source) {
707
+ target.prototype = Object.create(source.prototype);
708
+ target.prototype.constructor = target;
709
+ target.super_ = source;
710
+ };
711
+ FileUploader.FileLikeObject = FileLikeObject;
712
+ FileUploader.FileItem = FileItem;
713
+ FileUploader.FileDirective = FileDirective;
714
+ FileUploader.FileSelect = FileSelect;
715
+ FileUploader.FileDrop = FileDrop;
716
+ FileUploader.FileOver = FileOver;
717
+
718
+ // ---------------------------
719
+
720
+ /**
721
+ * Creates an instance of FileLikeObject
722
+ * @param {String} fakePath
723
+ * @constructor
724
+ */
725
+ function FileLikeObject(fakePath) {
726
+ var path = fakePath;
727
+ this.lastModifiedDate = null;
728
+ this.size = null;
729
+ this.type = 'like/' + path.slice(path.lastIndexOf('.') + 1).toLowerCase();
730
+ this.name = path.slice(path.lastIndexOf('/') + path.lastIndexOf('\\') + 2);
731
+ }
732
+
733
+ // ---------------------------
734
+
735
+ /**
736
+ * Creates an instance of FileItem
737
+ * @param {FileUploader} uploader
738
+ * @param {File|FileLikeObject|HTMLInputElement} file
739
+ * @param {File|Object} options
740
+ * @param {HTMLInputElement} [input]
741
+ * @constructor
742
+ */
743
+ function FileItem(uploader, file, options, input) {
744
+ file = uploader._getFileOrFileLikeObject(file);
745
+
746
+ angular.extend(this, {
747
+ url: uploader.url,
748
+ alias: uploader.alias,
749
+ headers: angular.copy(uploader.headers),
750
+ formData: angular.copy(uploader.formData),
751
+ removeAfterUpload: uploader.removeAfterUpload,
752
+ withCredentials: uploader.withCredentials,
753
+ method: uploader.method
754
+ }, options, {
755
+ uploader: uploader,
756
+ file: angular.copy(file),
757
+ isReady: false,
758
+ isUploading: false,
759
+ isUploaded: false,
760
+ isSuccess: false,
761
+ isCancel: false,
762
+ isError: false,
763
+ progress: 0,
764
+ index: null,
765
+ _file: file
766
+ });
767
+
768
+ if (input) {
769
+ this._input = angular.element(input);
770
+ this._replaceNode(this._input);
771
+ }
772
+ }
773
+ /**********************
774
+ * PUBLIC
775
+ **********************/
776
+ /**
777
+ * Uploads a FileItem
778
+ */
779
+ FileItem.prototype.upload = function() {
780
+ this.uploader.uploadItem(this);
781
+ };
782
+ /**
783
+ * Cancels uploading of FileItem
784
+ */
785
+ FileItem.prototype.cancel = function() {
786
+ this.uploader.cancelItem(this);
787
+ };
788
+ /**
789
+ * Removes a FileItem
790
+ */
791
+ FileItem.prototype.remove = function() {
792
+ this.uploader.removeFromQueue(this);
793
+ };
794
+ /**
795
+ * Callback
796
+ * @private
797
+ */
798
+ FileItem.prototype.onBeforeUpload = function() {};
799
+ /**
800
+ * Callback
801
+ * @param {Number} progress
802
+ * @private
803
+ */
804
+ FileItem.prototype.onProgress = function(progress) {};
805
+ /**
806
+ * Callback
807
+ * @param {*} response
808
+ * @param {Number} status
809
+ * @param {Object} headers
810
+ */
811
+ FileItem.prototype.onSuccess = function(response, status, headers) {};
812
+ /**
813
+ * Callback
814
+ * @param {*} response
815
+ * @param {Number} status
816
+ * @param {Object} headers
817
+ */
818
+ FileItem.prototype.onError = function(response, status, headers) {};
819
+ /**
820
+ * Callback
821
+ * @param {*} response
822
+ * @param {Number} status
823
+ * @param {Object} headers
824
+ */
825
+ FileItem.prototype.onCancel = function(response, status, headers) {};
826
+ /**
827
+ * Callback
828
+ * @param {*} response
829
+ * @param {Number} status
830
+ * @param {Object} headers
831
+ */
832
+ FileItem.prototype.onComplete = function(response, status, headers) {};
833
+ /**********************
834
+ * PRIVATE
835
+ **********************/
836
+ /**
837
+ * Inner callback
838
+ */
839
+ FileItem.prototype._onBeforeUpload = function() {
840
+ this.isReady = true;
841
+ this.isUploading = true;
842
+ this.isUploaded = false;
843
+ this.isSuccess = false;
844
+ this.isCancel = false;
845
+ this.isError = false;
846
+ this.progress = 0;
847
+ this.onBeforeUpload();
848
+ };
849
+ /**
850
+ * Inner callback
851
+ * @param {Number} progress
852
+ * @private
853
+ */
854
+ FileItem.prototype._onProgress = function(progress) {
855
+ this.progress = progress;
856
+ this.onProgress(progress);
857
+ };
858
+ /**
859
+ * Inner callback
860
+ * @param {*} response
861
+ * @param {Number} status
862
+ * @param {Object} headers
863
+ * @private
864
+ */
865
+ FileItem.prototype._onSuccess = function(response, status, headers) {
866
+ this.isReady = false;
867
+ this.isUploading = false;
868
+ this.isUploaded = true;
869
+ this.isSuccess = true;
870
+ this.isCancel = false;
871
+ this.isError = false;
872
+ this.progress = 100;
873
+ this.index = null;
874
+ this.onSuccess(response, status, headers);
875
+ };
876
+ /**
877
+ * Inner callback
878
+ * @param {*} response
879
+ * @param {Number} status
880
+ * @param {Object} headers
881
+ * @private
882
+ */
883
+ FileItem.prototype._onError = function(response, status, headers) {
884
+ this.isReady = false;
885
+ this.isUploading = false;
886
+ this.isUploaded = true;
887
+ this.isSuccess = false;
888
+ this.isCancel = false;
889
+ this.isError = true;
890
+ this.progress = 0;
891
+ this.index = null;
892
+ this.onError(response, status, headers);
893
+ };
894
+ /**
895
+ * Inner callback
896
+ * @param {*} response
897
+ * @param {Number} status
898
+ * @param {Object} headers
899
+ * @private
900
+ */
901
+ FileItem.prototype._onCancel = function(response, status, headers) {
902
+ this.isReady = false;
903
+ this.isUploading = false;
904
+ this.isUploaded = false;
905
+ this.isSuccess = false;
906
+ this.isCancel = true;
907
+ this.isError = false;
908
+ this.progress = 0;
909
+ this.index = null;
910
+ this.onCancel(response, status, headers);
911
+ };
912
+ /**
913
+ * Inner callback
914
+ * @param {*} response
915
+ * @param {Number} status
916
+ * @param {Object} headers
917
+ * @private
918
+ */
919
+ FileItem.prototype._onComplete = function(response, status, headers) {
920
+ this.onComplete(response, status, headers);
921
+ if (this.removeAfterUpload) this.remove();
922
+ };
923
+ /**
924
+ * Destroys a FileItem
925
+ */
926
+ FileItem.prototype._destroy = function() {
927
+ if (this._input) this._input.remove();
928
+ if (this._form) this._form.remove();
929
+ delete this._form;
930
+ delete this._input;
931
+ };
932
+ /**
933
+ * Prepares to uploading
934
+ * @private
935
+ */
936
+ FileItem.prototype._prepareToUploading = function() {
937
+ this.index = this.index || ++this.uploader._nextIndex;
938
+ this.isReady = true;
939
+ };
940
+ /**
941
+ * Replaces input element on his clone
942
+ * @param {JQLite|jQuery} input
943
+ * @private
944
+ */
945
+ FileItem.prototype._replaceNode = function(input) {
946
+ var clone = $compile(input.clone())(input.scope());
947
+ clone.prop('value', null); // FF fix
948
+ input.css('display', 'none');
949
+ input.after(clone); // remove jquery dependency
950
+ };
951
+
952
+ // ---------------------------
953
+
954
+ /**
955
+ * Creates instance of {FileDirective} object
956
+ * @param {Object} options
957
+ * @param {Object} options.uploader
958
+ * @param {HTMLElement} options.element
959
+ * @param {Object} options.events
960
+ * @param {String} options.prop
961
+ * @constructor
962
+ */
963
+ function FileDirective(options) {
964
+ angular.extend(this, options);
965
+ this.uploader._directives[this.prop].push(this);
966
+ this._saveLinks();
967
+ this.bind();
968
+ }
969
+ /**
970
+ * Map of events
971
+ * @type {Object}
972
+ */
973
+ FileDirective.prototype.events = {};
974
+ /**
975
+ * Binds events handles
976
+ */
977
+ FileDirective.prototype.bind = function() {
978
+ for(var key in this.events) {
979
+ var prop = this.events[key];
980
+ this.element.bind(key, this[prop]);
981
+ }
982
+ };
983
+ /**
984
+ * Unbinds events handles
985
+ */
986
+ FileDirective.prototype.unbind = function() {
987
+ for(var key in this.events) {
988
+ this.element.unbind(key, this.events[key]);
989
+ }
990
+ };
991
+ /**
992
+ * Destroys directive
993
+ */
994
+ FileDirective.prototype.destroy = function() {
995
+ var index = this.uploader._directives[this.prop].indexOf(this);
996
+ this.uploader._directives[this.prop].splice(index, 1);
997
+ this.unbind();
998
+ // this.element = null;
999
+ };
1000
+ /**
1001
+ * Saves links to functions
1002
+ * @private
1003
+ */
1004
+ FileDirective.prototype._saveLinks = function() {
1005
+ for(var key in this.events) {
1006
+ var prop = this.events[key];
1007
+ this[prop] = this[prop].bind(this);
1008
+ }
1009
+ };
1010
+
1011
+ // ---------------------------
1012
+
1013
+ FileUploader.inherit(FileSelect, FileDirective);
1014
+
1015
+ /**
1016
+ * Creates instance of {FileSelect} object
1017
+ * @param {Object} options
1018
+ * @constructor
1019
+ */
1020
+ function FileSelect(options) {
1021
+ FileSelect.super_.apply(this, arguments);
1022
+
1023
+ if(!this.uploader.isHTML5) {
1024
+ this.element.removeAttr('multiple');
1025
+ }
1026
+ this.element.prop('value', null); // FF fix
1027
+ }
1028
+ /**
1029
+ * Map of events
1030
+ * @type {Object}
1031
+ */
1032
+ FileSelect.prototype.events = {
1033
+ $destroy: 'destroy',
1034
+ change: 'onChange'
1035
+ };
1036
+ /**
1037
+ * Name of property inside uploader._directive object
1038
+ * @type {String}
1039
+ */
1040
+ FileSelect.prototype.prop = 'select';
1041
+ /**
1042
+ * Returns options
1043
+ * @return {Object|undefined}
1044
+ */
1045
+ FileSelect.prototype.getOptions = function() {};
1046
+ /**
1047
+ * Returns filters
1048
+ * @return {Array<Function>|String|undefined}
1049
+ */
1050
+ FileSelect.prototype.getFilters = function() {};
1051
+ /**
1052
+ * Event handler
1053
+ */
1054
+ FileSelect.prototype.onChange = function() {
1055
+ var files = this.uploader.isHTML5 ? this.element[0].files : this.element[0];
1056
+ var options = this.getOptions();
1057
+ var filters = this.getFilters();
1058
+
1059
+ if (!this.uploader.isHTML5) this.destroy();
1060
+ this.uploader.addToQueue(files, options, filters);
1061
+ if (this.uploader.isHTML5) this.element.prop('value', null);
1062
+ };
1063
+
1064
+ // ---------------------------
1065
+
1066
+ FileUploader.inherit(FileDrop, FileDirective);
1067
+
1068
+ /**
1069
+ * Creates instance of {FileDrop} object
1070
+ * @param {Object} options
1071
+ * @constructor
1072
+ */
1073
+ function FileDrop(options) {
1074
+ FileDrop.super_.apply(this, arguments);
1075
+ }
1076
+ /**
1077
+ * Map of events
1078
+ * @type {Object}
1079
+ */
1080
+ FileDrop.prototype.events = {
1081
+ $destroy: 'destroy',
1082
+ drop: 'onDrop',
1083
+ dragover: 'onDragOver',
1084
+ dragleave: 'onDragLeave'
1085
+ };
1086
+ /**
1087
+ * Name of property inside uploader._directive object
1088
+ * @type {String}
1089
+ */
1090
+ FileDrop.prototype.prop = 'drop';
1091
+ /**
1092
+ * Returns options
1093
+ * @return {Object|undefined}
1094
+ */
1095
+ FileDrop.prototype.getOptions = function() {};
1096
+ /**
1097
+ * Returns filters
1098
+ * @return {Array<Function>|String|undefined}
1099
+ */
1100
+ FileDrop.prototype.getFilters = function() {};
1101
+ /**
1102
+ * Event handler
1103
+ */
1104
+ FileDrop.prototype.onDrop = function(event) {
1105
+ var transfer = this._getTransfer(event);
1106
+ if (!transfer) return;
1107
+ var options = this.getOptions();
1108
+ var filters = this.getFilters();
1109
+ this._preventAndStop(event);
1110
+ angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
1111
+ this.uploader.addToQueue(transfer.files, options, filters);
1112
+ };
1113
+ /**
1114
+ * Event handler
1115
+ */
1116
+ FileDrop.prototype.onDragOver = function(event) {
1117
+ var transfer = this._getTransfer(event);
1118
+ if(!this._haveFiles(transfer.types)) return;
1119
+ transfer.dropEffect = 'copy';
1120
+ this._preventAndStop(event);
1121
+ angular.forEach(this.uploader._directives.over, this._addOverClass, this);
1122
+ };
1123
+ /**
1124
+ * Event handler
1125
+ */
1126
+ FileDrop.prototype.onDragLeave = function(event) {
1127
+ if (event.target !== this.element[0]) return;
1128
+ this._preventAndStop(event);
1129
+ angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
1130
+ };
1131
+ /**
1132
+ * Helper
1133
+ */
1134
+ FileDrop.prototype._getTransfer = function(event) {
1135
+ return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix;
1136
+ };
1137
+ /**
1138
+ * Helper
1139
+ */
1140
+ FileDrop.prototype._preventAndStop = function(event) {
1141
+ event.preventDefault();
1142
+ event.stopPropagation();
1143
+ };
1144
+ /**
1145
+ * Returns "true" if types contains files
1146
+ * @param {Object} types
1147
+ */
1148
+ FileDrop.prototype._haveFiles = function(types) {
1149
+ if (!types) return false;
1150
+ if (types.indexOf) {
1151
+ return types.indexOf('Files') !== -1;
1152
+ } else if(types.contains) {
1153
+ return types.contains('Files');
1154
+ } else {
1155
+ return false;
1156
+ }
1157
+ };
1158
+ /**
1159
+ * Callback
1160
+ */
1161
+ FileDrop.prototype._addOverClass = function(item) {
1162
+ item.addOverClass();
1163
+ };
1164
+ /**
1165
+ * Callback
1166
+ */
1167
+ FileDrop.prototype._removeOverClass = function(item) {
1168
+ item.removeOverClass();
1169
+ };
1170
+
1171
+ // ---------------------------
1172
+
1173
+ FileUploader.inherit(FileOver, FileDirective);
1174
+
1175
+ /**
1176
+ * Creates instance of {FileDrop} object
1177
+ * @param {Object} options
1178
+ * @constructor
1179
+ */
1180
+ function FileOver(options) {
1181
+ FileOver.super_.apply(this, arguments);
1182
+ }
1183
+ /**
1184
+ * Map of events
1185
+ * @type {Object}
1186
+ */
1187
+ FileOver.prototype.events = {
1188
+ $destroy: 'destroy'
1189
+ };
1190
+ /**
1191
+ * Name of property inside uploader._directive object
1192
+ * @type {String}
1193
+ */
1194
+ FileOver.prototype.prop = 'over';
1195
+ /**
1196
+ * Over class
1197
+ * @type {string}
1198
+ */
1199
+ FileOver.prototype.overClass = 'nv-file-over';
1200
+ /**
1201
+ * Adds over class
1202
+ */
1203
+ FileOver.prototype.addOverClass = function() {
1204
+ this.element.addClass(this.getOverClass());
1205
+ };
1206
+ /**
1207
+ * Removes over class
1208
+ */
1209
+ FileOver.prototype.removeOverClass = function() {
1210
+ this.element.removeClass(this.getOverClass());
1211
+ };
1212
+ /**
1213
+ * Returns over class
1214
+ * @returns {String}
1215
+ */
1216
+ FileOver.prototype.getOverClass = function() {
1217
+ return this.overClass;
1218
+ };
1219
+
1220
+ return FileUploader;
1221
+ }])
1222
+
1223
+
1224
+ .directive('nvFileSelect', ['$parse', 'FileUploader', function($parse, FileUploader) {
1225
+ return {
1226
+ link: function(scope, element, attributes) {
1227
+ var uploader = scope.$eval(attributes.uploader);
1228
+
1229
+ if (!(uploader instanceof FileUploader)) {
1230
+ throw new TypeError('"Uploader" must be an instance of FileUploader');
1231
+ }
1232
+
1233
+ var object = new FileUploader.FileSelect({
1234
+ uploader: uploader,
1235
+ element: element
1236
+ });
1237
+
1238
+ object.getOptions = $parse(attributes.options).bind(object, scope);
1239
+ object.getFilters = function() {return attributes.filters;};
1240
+ }
1241
+ };
1242
+ }])
1243
+
1244
+
1245
+ .directive('nvFileDrop', ['$parse', 'FileUploader', function($parse, FileUploader) {
1246
+ return {
1247
+ link: function(scope, element, attributes) {
1248
+ var uploader = scope.$eval(attributes.uploader);
1249
+
1250
+ if (!(uploader instanceof FileUploader)) {
1251
+ throw new TypeError('"Uploader" must be an instance of FileUploader');
1252
+ }
1253
+
1254
+ if (!uploader.isHTML5) return;
1255
+
1256
+ var object = new FileUploader.FileDrop({
1257
+ uploader: uploader,
1258
+ element: element
1259
+ });
1260
+
1261
+ object.getOptions = $parse(attributes.options).bind(object, scope);
1262
+ object.getFilters = function() {return attributes.filters;};
1263
+ }
1264
+ };
1265
+ }])
1266
+
1267
+
1268
+ .directive('nvFileOver', ['FileUploader', function(FileUploader) {
1269
+ return {
1270
+ link: function(scope, element, attributes) {
1271
+ var uploader = scope.$eval(attributes.uploader);
1272
+
1273
+ if (!(uploader instanceof FileUploader)) {
1274
+ throw new TypeError('"Uploader" must be an instance of FileUploader');
1275
+ }
1276
+
1277
+ var object = new FileUploader.FileOver({
1278
+ uploader: uploader,
1279
+ element: element
1280
+ });
1281
+
1282
+ object.getOverClass = function() {
1283
+ return attributes.overClass || this.overClass;
1284
+ };
1285
+ }
1286
+ };
1287
+ }])
1288
+ return module;
1289
+ }));