angularjs-file-upload-rails 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }));