UNPKG

50.2 kBJavaScriptView Raw
1/*
2 angular-file-upload v1.1.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, function(angular) {
14
15var module = angular.module('angularFileUpload', []);
16
17'use strict';
18
19/**
20 * Classes
21 *
22 * FileUploader
23 * FileUploader.FileLikeObject
24 * FileUploader.FileItem
25 * FileUploader.FileDirective
26 * FileUploader.FileSelect
27 * FileUploader.FileDrop
28 * FileUploader.FileOver
29 */
30
31module
32
33
34 .value('fileUploaderOptions', {
35 url: '/',
36 alias: 'file',
37 headers: {},
38 queue: [],
39 progress: 0,
40 autoUpload: false,
41 removeAfterUpload: false,
42 method: 'POST',
43 filters: [],
44 formData: [],
45 queueLimit: Number.MAX_VALUE,
46 withCredentials: false
47 })
48
49
50 .factory('FileUploader', ['fileUploaderOptions', '$rootScope', '$http', '$window', '$compile',
51 function(fileUploaderOptions, $rootScope, $http, $window, $compile) {
52 /**
53 * Creates an instance of FileUploader
54 * @param {Object} [options]
55 * @constructor
56 */
57 function FileUploader(options) {
58 var settings = angular.copy(fileUploaderOptions);
59 angular.extend(this, settings, options, {
60 isUploading: false,
61 _nextIndex: 0,
62 _failFilterIndex: -1,
63 _directives: {select: [], drop: [], over: []}
64 });
65
66 // add default filters
67 this.filters.unshift({name: 'queueLimit', fn: this._queueLimitFilter});
68 this.filters.unshift({name: 'folder', fn: this._folderFilter});
69 }
70 /**********************
71 * PUBLIC
72 **********************/
73 /**
74 * Checks a support the html5 uploader
75 * @returns {Boolean}
76 * @readonly
77 */
78 FileUploader.prototype.isHTML5 = !!($window.File && $window.FormData);
79 /**
80 * Adds items to the queue
81 * @param {File|HTMLInputElement|Object|FileList|Array<Object>} files
82 * @param {Object} [options]
83 * @param {Array<Function>|String} filters
84 */
85 FileUploader.prototype.addToQueue = function(files, options, filters) {
86 var list = this.isArrayLikeObject(files) ? files: [files];
87 var arrayOfFilters = this._getFilters(filters);
88 var count = this.queue.length;
89 var addedFileItems = [];
90
91 angular.forEach(list, function(some /*{File|HTMLInputElement|Object}*/) {
92 var temp = new FileUploader.FileLikeObject(some);
93
94 if (this._isValidFile(temp, arrayOfFilters, options)) {
95 var fileItem = new FileUploader.FileItem(this, some, options);
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(temp, 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 "true" if value is array like object
203 * @param {*} value
204 * @returns {Boolean}
205 */
206 FileUploader.prototype.isArrayLikeObject = function(value) {
207 return (angular.isObject(value) && 'length' in value);
208 };
209 /**
210 * Returns a index of item from the queue
211 * @param {Item|Number} value
212 * @returns {Number}
213 */
214 FileUploader.prototype.getIndexOfItem = function(value) {
215 return angular.isNumber(value) ? value : this.queue.indexOf(value);
216 };
217 /**
218 * Returns not uploaded items
219 * @returns {Array}
220 */
221 FileUploader.prototype.getNotUploadedItems = function() {
222 return this.queue.filter(function(item) {
223 return !item.isUploaded;
224 });
225 };
226 /**
227 * Returns items ready for upload
228 * @returns {Array}
229 */
230 FileUploader.prototype.getReadyItems = function() {
231 return this.queue
232 .filter(function(item) {
233 return (item.isReady && !item.isUploading);
234 })
235 .sort(function(item1, item2) {
236 return item1.index - item2.index;
237 });
238 };
239 /**
240 * Destroys instance of FileUploader
241 */
242 FileUploader.prototype.destroy = function() {
243 angular.forEach(this._directives, function(key) {
244 angular.forEach(this._directives[key], function(object) {
245 object.destroy();
246 }, this);
247 }, this);
248 };
249 /**
250 * Callback
251 * @param {Array} fileItems
252 */
253 FileUploader.prototype.onAfterAddingAll = function(fileItems) {};
254 /**
255 * Callback
256 * @param {FileItem} fileItem
257 */
258 FileUploader.prototype.onAfterAddingFile = function(fileItem) {};
259 /**
260 * Callback
261 * @param {File|Object} item
262 * @param {Object} filter
263 * @param {Object} options
264 * @private
265 */
266 FileUploader.prototype.onWhenAddingFileFailed = function(item, filter, options) {};
267 /**
268 * Callback
269 * @param {FileItem} fileItem
270 */
271 FileUploader.prototype.onBeforeUploadItem = function(fileItem) {};
272 /**
273 * Callback
274 * @param {FileItem} fileItem
275 * @param {Number} progress
276 */
277 FileUploader.prototype.onProgressItem = function(fileItem, progress) {};
278 /**
279 * Callback
280 * @param {Number} progress
281 */
282 FileUploader.prototype.onProgressAll = function(progress) {};
283 /**
284 * Callback
285 * @param {FileItem} item
286 * @param {*} response
287 * @param {Number} status
288 * @param {Object} headers
289 */
290 FileUploader.prototype.onSuccessItem = 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.onErrorItem = 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.onCancelItem = function(item, response, status, headers) {};
307 /**
308 * Callback
309 * @param {FileItem} item
310 * @param {*} response
311 * @param {Number} status
312 * @param {Object} headers
313 */
314 FileUploader.prototype.onCompleteItem = function(item, response, status, headers) {};
315 /**
316 * Callback
317 */
318 FileUploader.prototype.onCompleteAll = function() {};
319 /**********************
320 * PRIVATE
321 **********************/
322 /**
323 * Returns the total progress
324 * @param {Number} [value]
325 * @returns {Number}
326 * @private
327 */
328 FileUploader.prototype._getTotalProgress = function(value) {
329 if(this.removeAfterUpload) return value || 0;
330
331 var notUploaded = this.getNotUploadedItems().length;
332 var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length;
333 var ratio = 100 / this.queue.length;
334 var current = (value || 0) * ratio / 100;
335
336 return Math.round(uploaded * ratio + current);
337 };
338 /**
339 * Returns array of filters
340 * @param {Array<Function>|String} filters
341 * @returns {Array<Function>}
342 * @private
343 */
344 FileUploader.prototype._getFilters = function(filters) {
345 if (angular.isUndefined(filters)) return this.filters;
346 if (angular.isArray(filters)) return filters;
347 var names = filters.split(/\s*,/);
348 return this.filters.filter(function(filter) {
349 return names.indexOf(filter.name) !== -1;
350 }, this);
351 };
352 /**
353 * Updates html
354 * @private
355 */
356 FileUploader.prototype._render = function() {
357 if (!$rootScope.$$phase) $rootScope.$apply();
358 };
359 /**
360 * Returns "true" if item is a file (not folder)
361 * @param {File|FileLikeObject} item
362 * @returns {Boolean}
363 * @private
364 */
365 FileUploader.prototype._folderFilter = function(item) {
366 return !!(item.size || item.type);
367 };
368 /**
369 * Returns "true" if the limit has not been reached
370 * @returns {Boolean}
371 * @private
372 */
373 FileUploader.prototype._queueLimitFilter = function() {
374 return this.queue.length < this.queueLimit;
375 };
376 /**
377 * Returns "true" if file pass all filters
378 * @param {File|Object} file
379 * @param {Array<Function>} filters
380 * @param {Object} options
381 * @returns {Boolean}
382 * @private
383 */
384 FileUploader.prototype._isValidFile = function(file, filters, options) {
385 this._failFilterIndex = -1;
386 return !filters.length ? true : filters.every(function(filter) {
387 this._failFilterIndex++;
388 return filter.fn.call(this, file, options);
389 }, this);
390 };
391 /**
392 * Checks whether upload successful
393 * @param {Number} status
394 * @returns {Boolean}
395 * @private
396 */
397 FileUploader.prototype._isSuccessCode = function(status) {
398 return (status >= 200 && status < 300) || status === 304;
399 };
400 /**
401 * Transforms the server response
402 * @param {*} response
403 * @returns {*}
404 * @private
405 */
406 FileUploader.prototype._transformResponse = function(response) {
407 angular.forEach($http.defaults.transformResponse, function(transformFn) {
408 response = transformFn(response);
409 });
410 return response;
411 };
412 /**
413 * Parsed response headers
414 * @param headers
415 * @returns {Object}
416 * @see https://github.com/angular/angular.js/blob/master/src/ng/http.js
417 * @private
418 */
419 FileUploader.prototype._parseHeaders = function(headers) {
420 var parsed = {}, key, val, i;
421
422 if (!headers) return parsed;
423
424 function trim(string) {
425 return string.replace(/^\s+/, '').replace(/\s+$/, '');
426 }
427 function lowercase(string) {
428 return string.toLowerCase();
429 }
430
431 angular.forEach(headers.split('\n'), function(line) {
432 i = line.indexOf(':');
433 key = lowercase(trim(line.substr(0, i)));
434 val = trim(line.substr(i + 1));
435
436 if (key) {
437 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
438 }
439 });
440
441 return parsed;
442 };
443 /**
444 * The XMLHttpRequest transport
445 * @param {FileItem} item
446 * @private
447 */
448 FileUploader.prototype._xhrTransport = function(item) {
449 var xhr = item._xhr = new XMLHttpRequest();
450 var form = new FormData();
451 var that = this;
452
453 that._onBeforeUploadItem(item);
454
455 angular.forEach(item.formData, function(obj) {
456 angular.forEach(obj, function(value, key) {
457 form.append(key, value);
458 });
459 });
460
461 form.append(item.alias, item._file, item.file.name);
462
463 xhr.upload.onprogress = function(event) {
464 var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
465 that._onProgressItem(item, progress);
466 };
467
468 xhr.onload = function() {
469 var headers = that._parseHeaders(xhr.getAllResponseHeaders());
470 var response = that._transformResponse(xhr.response);
471 var gist = that._isSuccessCode(xhr.status) ? 'Success' : 'Error';
472 var method = '_on' + gist + 'Item';
473 that[method](item, response, xhr.status, headers);
474 that._onCompleteItem(item, response, xhr.status, headers);
475 };
476
477 xhr.onerror = function() {
478 var headers = that._parseHeaders(xhr.getAllResponseHeaders());
479 var response = that._transformResponse(xhr.response);
480 that._onErrorItem(item, response, xhr.status, headers);
481 that._onCompleteItem(item, response, xhr.status, headers);
482 };
483
484 xhr.onabort = function() {
485 var headers = that._parseHeaders(xhr.getAllResponseHeaders());
486 var response = that._transformResponse(xhr.response);
487 that._onCancelItem(item, response, xhr.status, headers);
488 that._onCompleteItem(item, response, xhr.status, headers);
489 };
490
491 xhr.open(item.method, item.url, true);
492
493 xhr.withCredentials = item.withCredentials;
494
495 angular.forEach(item.headers, function(value, name) {
496 xhr.setRequestHeader(name, value);
497 });
498
499 xhr.send(form);
500 this._render();
501 };
502 /**
503 * The IFrame transport
504 * @param {FileItem} item
505 * @private
506 */
507 FileUploader.prototype._iframeTransport = function(item) {
508 var form = angular.element('<form style="display: none;" />');
509 var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
510 var input = item._input;
511 var that = this;
512
513 if (item._form) item._form.replaceWith(input); // remove old form
514 item._form = form; // save link to new form
515
516 that._onBeforeUploadItem(item);
517
518 input.prop('name', item.alias);
519
520 angular.forEach(item.formData, function(obj) {
521 angular.forEach(obj, function(value, key) {
522 form.append(angular.element('<input type="hidden" name="' + key + '" value="' + value + '" />'));
523 });
524 });
525
526 form.prop({
527 action: item.url,
528 method: 'POST',
529 target: iframe.prop('name'),
530 enctype: 'multipart/form-data',
531 encoding: 'multipart/form-data' // old IE
532 });
533
534 iframe.bind('load', function() {
535 try {
536 // Fix for legacy IE browsers that loads internal error page
537 // when failed WS response received. In consequence iframe
538 // content access denied error is thrown becouse trying to
539 // access cross domain page. When such thing occurs notifying
540 // with empty response object. See more info at:
541 // http://stackoverflow.com/questions/151362/access-is-denied-error-on-accessing-iframe-document-object
542 // Note that if non standard 4xx or 5xx error code returned
543 // from WS then response content can be accessed without error
544 // but 'XHR' status becomes 200. In order to avoid confusion
545 // returning response via same 'success' event handler.
546
547 // fixed angular.contents() for iframes
548 var html = iframe[0].contentDocument.body.innerHTML;
549 } catch (e) {}
550
551 var xhr = {response: html, status: 200, dummy: true};
552 var response = that._transformResponse(xhr.response);
553 var headers = {};
554
555 that._onSuccessItem(item, response, xhr.status, headers);
556 that._onCompleteItem(item, response, xhr.status, headers);
557 });
558
559 form.abort = function() {
560 var xhr = {status: 0, dummy: true};
561 var headers = {};
562 var response;
563
564 iframe.unbind('load').prop('src', 'javascript:false;');
565 form.replaceWith(input);
566
567 that._onCancelItem(item, response, xhr.status, headers);
568 that._onCompleteItem(item, response, xhr.status, headers);
569 };
570
571 input.after(form);
572 form.append(input).append(iframe);
573
574 form[0].submit();
575 this._render();
576 };
577 /**
578 * Inner callback
579 * @param {File|Object} item
580 * @param {Object} filter
581 * @param {Object} options
582 * @private
583 */
584 FileUploader.prototype._onWhenAddingFileFailed = function(item, filter, options) {
585 this.onWhenAddingFileFailed(item, filter, options);
586 };
587 /**
588 * Inner callback
589 * @param {FileItem} item
590 */
591 FileUploader.prototype._onAfterAddingFile = function(item) {
592 this.onAfterAddingFile(item);
593 };
594 /**
595 * Inner callback
596 * @param {Array<FileItem>} items
597 */
598 FileUploader.prototype._onAfterAddingAll = function(items) {
599 this.onAfterAddingAll(items);
600 };
601 /**
602 * Inner callback
603 * @param {FileItem} item
604 * @private
605 */
606 FileUploader.prototype._onBeforeUploadItem = function(item) {
607 item._onBeforeUpload();
608 this.onBeforeUploadItem(item);
609 };
610 /**
611 * Inner callback
612 * @param {FileItem} item
613 * @param {Number} progress
614 * @private
615 */
616 FileUploader.prototype._onProgressItem = function(item, progress) {
617 var total = this._getTotalProgress(progress);
618 this.progress = total;
619 item._onProgress(progress);
620 this.onProgressItem(item, progress);
621 this.onProgressAll(total);
622 this._render();
623 };
624 /**
625 * Inner callback
626 * @param {FileItem} item
627 * @param {*} response
628 * @param {Number} status
629 * @param {Object} headers
630 * @private
631 */
632 FileUploader.prototype._onSuccessItem = function(item, response, status, headers) {
633 item._onSuccess(response, status, headers);
634 this.onSuccessItem(item, response, status, headers);
635 };
636 /**
637 * Inner callback
638 * @param {FileItem} item
639 * @param {*} response
640 * @param {Number} status
641 * @param {Object} headers
642 * @private
643 */
644 FileUploader.prototype._onErrorItem = function(item, response, status, headers) {
645 item._onError(response, status, headers);
646 this.onErrorItem(item, response, status, headers);
647 };
648 /**
649 * Inner callback
650 * @param {FileItem} item
651 * @param {*} response
652 * @param {Number} status
653 * @param {Object} headers
654 * @private
655 */
656 FileUploader.prototype._onCancelItem = function(item, response, status, headers) {
657 item._onCancel(response, status, headers);
658 this.onCancelItem(item, response, status, headers);
659 };
660 /**
661 * Inner callback
662 * @param {FileItem} item
663 * @param {*} response
664 * @param {Number} status
665 * @param {Object} headers
666 * @private
667 */
668 FileUploader.prototype._onCompleteItem = function(item, response, status, headers) {
669 item._onComplete(response, status, headers);
670 this.onCompleteItem(item, response, status, headers);
671
672 var nextItem = this.getReadyItems()[0];
673 this.isUploading = false;
674
675 if(angular.isDefined(nextItem)) {
676 nextItem.upload();
677 return;
678 }
679
680 this.onCompleteAll();
681 this.progress = this._getTotalProgress();
682 this._render();
683 };
684 /**********************
685 * STATIC
686 **********************/
687 /**
688 * @borrows FileUploader.prototype.isFile
689 */
690 FileUploader.isFile = FileUploader.prototype.isFile;
691 /**
692 * @borrows FileUploader.prototype.isFileLikeObject
693 */
694 FileUploader.isFileLikeObject = FileUploader.prototype.isFileLikeObject;
695 /**
696 * @borrows FileUploader.prototype.isArrayLikeObject
697 */
698 FileUploader.isArrayLikeObject = FileUploader.prototype.isArrayLikeObject;
699 /**
700 * @borrows FileUploader.prototype.isHTML5
701 */
702 FileUploader.isHTML5 = FileUploader.prototype.isHTML5;
703 /**
704 * Inherits a target (Class_1) by a source (Class_2)
705 * @param {Function} target
706 * @param {Function} source
707 */
708 FileUploader.inherit = function(target, source) {
709 target.prototype = Object.create(source.prototype);
710 target.prototype.constructor = target;
711 target.super_ = source;
712 };
713 FileUploader.FileLikeObject = FileLikeObject;
714 FileUploader.FileItem = FileItem;
715 FileUploader.FileDirective = FileDirective;
716 FileUploader.FileSelect = FileSelect;
717 FileUploader.FileDrop = FileDrop;
718 FileUploader.FileOver = FileOver;
719
720 // ---------------------------
721
722 /**
723 * Creates an instance of FileLikeObject
724 * @param {File|HTMLInputElement|Object} fileOrInput
725 * @constructor
726 */
727 function FileLikeObject(fileOrInput) {
728 var isInput = angular.isElement(fileOrInput);
729 var fakePathOrObject = isInput ? fileOrInput.value : fileOrInput;
730 var postfix = angular.isString(fakePathOrObject) ? 'FakePath' : 'Object';
731 var method = '_createFrom' + postfix;
732 this[method](fakePathOrObject);
733 }
734
735 /**
736 * Creates file like object from fake path string
737 * @param {String} path
738 * @private
739 */
740 FileLikeObject.prototype._createFromFakePath = function(path) {
741 this.lastModifiedDate = null;
742 this.size = null;
743 this.type = 'like/' + path.slice(path.lastIndexOf('.') + 1).toLowerCase();
744 this.name = path.slice(path.lastIndexOf('/') + path.lastIndexOf('\\') + 2);
745 };
746 /**
747 * Creates file like object from object
748 * @param {File|FileLikeObject} object
749 * @private
750 */
751 FileLikeObject.prototype._createFromObject = function(object) {
752 this.lastModifiedDate = angular.copy(object.lastModifiedDate);
753 this.size = object.size;
754 this.type = object.type;
755 this.name = object.name;
756 };
757
758 // ---------------------------
759
760 /**
761 * Creates an instance of FileItem
762 * @param {FileUploader} uploader
763 * @param {File|HTMLInputElement|Object} some
764 * @param {Object} options
765 * @constructor
766 */
767 function FileItem(uploader, some, options) {
768 var isInput = angular.isElement(some);
769 var input = isInput ? angular.element(some) : null;
770 var file = !isInput ? some : null;
771
772 angular.extend(this, {
773 url: uploader.url,
774 alias: uploader.alias,
775 headers: angular.copy(uploader.headers),
776 formData: angular.copy(uploader.formData),
777 removeAfterUpload: uploader.removeAfterUpload,
778 withCredentials: uploader.withCredentials,
779 method: uploader.method
780 }, options, {
781 uploader: uploader,
782 file: new FileUploader.FileLikeObject(some),
783 isReady: false,
784 isUploading: false,
785 isUploaded: false,
786 isSuccess: false,
787 isCancel: false,
788 isError: false,
789 progress: 0,
790 index: null,
791 _file: file,
792 _input: input
793 });
794
795 if (input) this._replaceNode(input);
796 }
797 /**********************
798 * PUBLIC
799 **********************/
800 /**
801 * Uploads a FileItem
802 */
803 FileItem.prototype.upload = function() {
804 this.uploader.uploadItem(this);
805 };
806 /**
807 * Cancels uploading of FileItem
808 */
809 FileItem.prototype.cancel = function() {
810 this.uploader.cancelItem(this);
811 };
812 /**
813 * Removes a FileItem
814 */
815 FileItem.prototype.remove = function() {
816 this.uploader.removeFromQueue(this);
817 };
818 /**
819 * Callback
820 * @private
821 */
822 FileItem.prototype.onBeforeUpload = function() {};
823 /**
824 * Callback
825 * @param {Number} progress
826 * @private
827 */
828 FileItem.prototype.onProgress = function(progress) {};
829 /**
830 * Callback
831 * @param {*} response
832 * @param {Number} status
833 * @param {Object} headers
834 */
835 FileItem.prototype.onSuccess = function(response, status, headers) {};
836 /**
837 * Callback
838 * @param {*} response
839 * @param {Number} status
840 * @param {Object} headers
841 */
842 FileItem.prototype.onError = function(response, status, headers) {};
843 /**
844 * Callback
845 * @param {*} response
846 * @param {Number} status
847 * @param {Object} headers
848 */
849 FileItem.prototype.onCancel = function(response, status, headers) {};
850 /**
851 * Callback
852 * @param {*} response
853 * @param {Number} status
854 * @param {Object} headers
855 */
856 FileItem.prototype.onComplete = function(response, status, headers) {};
857 /**********************
858 * PRIVATE
859 **********************/
860 /**
861 * Inner callback
862 */
863 FileItem.prototype._onBeforeUpload = function() {
864 this.isReady = true;
865 this.isUploading = true;
866 this.isUploaded = false;
867 this.isSuccess = false;
868 this.isCancel = false;
869 this.isError = false;
870 this.progress = 0;
871 this.onBeforeUpload();
872 };
873 /**
874 * Inner callback
875 * @param {Number} progress
876 * @private
877 */
878 FileItem.prototype._onProgress = function(progress) {
879 this.progress = progress;
880 this.onProgress(progress);
881 };
882 /**
883 * Inner callback
884 * @param {*} response
885 * @param {Number} status
886 * @param {Object} headers
887 * @private
888 */
889 FileItem.prototype._onSuccess = function(response, status, headers) {
890 this.isReady = false;
891 this.isUploading = false;
892 this.isUploaded = true;
893 this.isSuccess = true;
894 this.isCancel = false;
895 this.isError = false;
896 this.progress = 100;
897 this.index = null;
898 this.onSuccess(response, status, headers);
899 };
900 /**
901 * Inner callback
902 * @param {*} response
903 * @param {Number} status
904 * @param {Object} headers
905 * @private
906 */
907 FileItem.prototype._onError = function(response, status, headers) {
908 this.isReady = false;
909 this.isUploading = false;
910 this.isUploaded = true;
911 this.isSuccess = false;
912 this.isCancel = false;
913 this.isError = true;
914 this.progress = 0;
915 this.index = null;
916 this.onError(response, status, headers);
917 };
918 /**
919 * Inner callback
920 * @param {*} response
921 * @param {Number} status
922 * @param {Object} headers
923 * @private
924 */
925 FileItem.prototype._onCancel = function(response, status, headers) {
926 this.isReady = false;
927 this.isUploading = false;
928 this.isUploaded = false;
929 this.isSuccess = false;
930 this.isCancel = true;
931 this.isError = false;
932 this.progress = 0;
933 this.index = null;
934 this.onCancel(response, status, headers);
935 };
936 /**
937 * Inner callback
938 * @param {*} response
939 * @param {Number} status
940 * @param {Object} headers
941 * @private
942 */
943 FileItem.prototype._onComplete = function(response, status, headers) {
944 this.onComplete(response, status, headers);
945 if (this.removeAfterUpload) this.remove();
946 };
947 /**
948 * Destroys a FileItem
949 */
950 FileItem.prototype._destroy = function() {
951 if (this._input) this._input.remove();
952 if (this._form) this._form.remove();
953 delete this._form;
954 delete this._input;
955 };
956 /**
957 * Prepares to uploading
958 * @private
959 */
960 FileItem.prototype._prepareToUploading = function() {
961 this.index = this.index || ++this.uploader._nextIndex;
962 this.isReady = true;
963 };
964 /**
965 * Replaces input element on his clone
966 * @param {JQLite|jQuery} input
967 * @private
968 */
969 FileItem.prototype._replaceNode = function(input) {
970 var clone = $compile(input.clone())(input.scope());
971 clone.prop('value', null); // FF fix
972 input.css('display', 'none');
973 input.after(clone); // remove jquery dependency
974 };
975
976 // ---------------------------
977
978 /**
979 * Creates instance of {FileDirective} object
980 * @param {Object} options
981 * @param {Object} options.uploader
982 * @param {HTMLElement} options.element
983 * @param {Object} options.events
984 * @param {String} options.prop
985 * @constructor
986 */
987 function FileDirective(options) {
988 angular.extend(this, options);
989 this.uploader._directives[this.prop].push(this);
990 this._saveLinks();
991 this.bind();
992 }
993 /**
994 * Map of events
995 * @type {Object}
996 */
997 FileDirective.prototype.events = {};
998 /**
999 * Binds events handles
1000 */
1001 FileDirective.prototype.bind = function() {
1002 for(var key in this.events) {
1003 var prop = this.events[key];
1004 this.element.bind(key, this[prop]);
1005 }
1006 };
1007 /**
1008 * Unbinds events handles
1009 */
1010 FileDirective.prototype.unbind = function() {
1011 for(var key in this.events) {
1012 this.element.unbind(key, this.events[key]);
1013 }
1014 };
1015 /**
1016 * Destroys directive
1017 */
1018 FileDirective.prototype.destroy = function() {
1019 var index = this.uploader._directives[this.prop].indexOf(this);
1020 this.uploader._directives[this.prop].splice(index, 1);
1021 this.unbind();
1022 // this.element = null;
1023 };
1024 /**
1025 * Saves links to functions
1026 * @private
1027 */
1028 FileDirective.prototype._saveLinks = function() {
1029 for(var key in this.events) {
1030 var prop = this.events[key];
1031 this[prop] = this[prop].bind(this);
1032 }
1033 };
1034
1035 // ---------------------------
1036
1037 FileUploader.inherit(FileSelect, FileDirective);
1038
1039 /**
1040 * Creates instance of {FileSelect} object
1041 * @param {Object} options
1042 * @constructor
1043 */
1044 function FileSelect(options) {
1045 FileSelect.super_.apply(this, arguments);
1046
1047 if(!this.uploader.isHTML5) {
1048 this.element.removeAttr('multiple');
1049 }
1050 this.element.prop('value', null); // FF fix
1051 }
1052 /**
1053 * Map of events
1054 * @type {Object}
1055 */
1056 FileSelect.prototype.events = {
1057 $destroy: 'destroy',
1058 change: 'onChange'
1059 };
1060 /**
1061 * Name of property inside uploader._directive object
1062 * @type {String}
1063 */
1064 FileSelect.prototype.prop = 'select';
1065 /**
1066 * Returns options
1067 * @return {Object|undefined}
1068 */
1069 FileSelect.prototype.getOptions = function() {};
1070 /**
1071 * Returns filters
1072 * @return {Array<Function>|String|undefined}
1073 */
1074 FileSelect.prototype.getFilters = function() {};
1075 /**
1076 * If returns "true" then HTMLInputElement will be cleared
1077 * @returns {Boolean}
1078 */
1079 FileSelect.prototype.isEmptyAfterSelection = function() {
1080 return !!this.element.attr('multiple');
1081 };
1082 /**
1083 * Event handler
1084 */
1085 FileSelect.prototype.onChange = function() {
1086 var files = this.uploader.isHTML5 ? this.element[0].files : this.element[0];
1087 var options = this.getOptions();
1088 var filters = this.getFilters();
1089
1090 if (!this.uploader.isHTML5) this.destroy();
1091 this.uploader.addToQueue(files, options, filters);
1092 if (this.isEmptyAfterSelection()) this.element.prop('value', null);
1093 };
1094
1095 // ---------------------------
1096
1097 FileUploader.inherit(FileDrop, FileDirective);
1098
1099 /**
1100 * Creates instance of {FileDrop} object
1101 * @param {Object} options
1102 * @constructor
1103 */
1104 function FileDrop(options) {
1105 FileDrop.super_.apply(this, arguments);
1106 }
1107 /**
1108 * Map of events
1109 * @type {Object}
1110 */
1111 FileDrop.prototype.events = {
1112 $destroy: 'destroy',
1113 drop: 'onDrop',
1114 dragover: 'onDragOver',
1115 dragleave: 'onDragLeave'
1116 };
1117 /**
1118 * Name of property inside uploader._directive object
1119 * @type {String}
1120 */
1121 FileDrop.prototype.prop = 'drop';
1122 /**
1123 * Returns options
1124 * @return {Object|undefined}
1125 */
1126 FileDrop.prototype.getOptions = function() {};
1127 /**
1128 * Returns filters
1129 * @return {Array<Function>|String|undefined}
1130 */
1131 FileDrop.prototype.getFilters = function() {};
1132 /**
1133 * Event handler
1134 */
1135 FileDrop.prototype.onDrop = function(event) {
1136 var transfer = this._getTransfer(event);
1137 if (!transfer) return;
1138 var options = this.getOptions();
1139 var filters = this.getFilters();
1140 this._preventAndStop(event);
1141 angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
1142 this.uploader.addToQueue(transfer.files, options, filters);
1143 };
1144 /**
1145 * Event handler
1146 */
1147 FileDrop.prototype.onDragOver = function(event) {
1148 var transfer = this._getTransfer(event);
1149 if(!this._haveFiles(transfer.types)) return;
1150 transfer.dropEffect = 'copy';
1151 this._preventAndStop(event);
1152 angular.forEach(this.uploader._directives.over, this._addOverClass, this);
1153 };
1154 /**
1155 * Event handler
1156 */
1157 FileDrop.prototype.onDragLeave = function(event) {
1158 if (event.target !== this.element[0]) return;
1159 this._preventAndStop(event);
1160 angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
1161 };
1162 /**
1163 * Helper
1164 */
1165 FileDrop.prototype._getTransfer = function(event) {
1166 return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix;
1167 };
1168 /**
1169 * Helper
1170 */
1171 FileDrop.prototype._preventAndStop = function(event) {
1172 event.preventDefault();
1173 event.stopPropagation();
1174 };
1175 /**
1176 * Returns "true" if types contains files
1177 * @param {Object} types
1178 */
1179 FileDrop.prototype._haveFiles = function(types) {
1180 if (!types) return false;
1181 if (types.indexOf) {
1182 return types.indexOf('Files') !== -1;
1183 } else if(types.contains) {
1184 return types.contains('Files');
1185 } else {
1186 return false;
1187 }
1188 };
1189 /**
1190 * Callback
1191 */
1192 FileDrop.prototype._addOverClass = function(item) {
1193 item.addOverClass();
1194 };
1195 /**
1196 * Callback
1197 */
1198 FileDrop.prototype._removeOverClass = function(item) {
1199 item.removeOverClass();
1200 };
1201
1202 // ---------------------------
1203
1204 FileUploader.inherit(FileOver, FileDirective);
1205
1206 /**
1207 * Creates instance of {FileDrop} object
1208 * @param {Object} options
1209 * @constructor
1210 */
1211 function FileOver(options) {
1212 FileOver.super_.apply(this, arguments);
1213 }
1214 /**
1215 * Map of events
1216 * @type {Object}
1217 */
1218 FileOver.prototype.events = {
1219 $destroy: 'destroy'
1220 };
1221 /**
1222 * Name of property inside uploader._directive object
1223 * @type {String}
1224 */
1225 FileOver.prototype.prop = 'over';
1226 /**
1227 * Over class
1228 * @type {string}
1229 */
1230 FileOver.prototype.overClass = 'nv-file-over';
1231 /**
1232 * Adds over class
1233 */
1234 FileOver.prototype.addOverClass = function() {
1235 this.element.addClass(this.getOverClass());
1236 };
1237 /**
1238 * Removes over class
1239 */
1240 FileOver.prototype.removeOverClass = function() {
1241 this.element.removeClass(this.getOverClass());
1242 };
1243 /**
1244 * Returns over class
1245 * @returns {String}
1246 */
1247 FileOver.prototype.getOverClass = function() {
1248 return this.overClass;
1249 };
1250
1251 return FileUploader;
1252 }])
1253
1254
1255 .directive('nvFileSelect', ['$parse', 'FileUploader', function($parse, FileUploader) {
1256 return {
1257 link: function(scope, element, attributes) {
1258 var uploader = scope.$eval(attributes.uploader);
1259
1260 if (!(uploader instanceof FileUploader)) {
1261 throw new TypeError('"Uploader" must be an instance of FileUploader');
1262 }
1263
1264 var object = new FileUploader.FileSelect({
1265 uploader: uploader,
1266 element: element
1267 });
1268
1269 object.getOptions = $parse(attributes.options).bind(object, scope);
1270 object.getFilters = function() {return attributes.filters;};
1271 }
1272 };
1273 }])
1274
1275
1276 .directive('nvFileDrop', ['$parse', 'FileUploader', function($parse, FileUploader) {
1277 return {
1278 link: function(scope, element, attributes) {
1279 var uploader = scope.$eval(attributes.uploader);
1280
1281 if (!(uploader instanceof FileUploader)) {
1282 throw new TypeError('"Uploader" must be an instance of FileUploader');
1283 }
1284
1285 if (!uploader.isHTML5) return;
1286
1287 var object = new FileUploader.FileDrop({
1288 uploader: uploader,
1289 element: element
1290 });
1291
1292 object.getOptions = $parse(attributes.options).bind(object, scope);
1293 object.getFilters = function() {return attributes.filters;};
1294 }
1295 };
1296 }])
1297
1298
1299 .directive('nvFileOver', ['FileUploader', function(FileUploader) {
1300 return {
1301 link: function(scope, element, attributes) {
1302 var uploader = scope.$eval(attributes.uploader);
1303
1304 if (!(uploader instanceof FileUploader)) {
1305 throw new TypeError('"Uploader" must be an instance of FileUploader');
1306 }
1307
1308 var object = new FileUploader.FileOver({
1309 uploader: uploader,
1310 element: element
1311 });
1312
1313 object.getOverClass = function() {
1314 return attributes.overClass || this.overClass;
1315 };
1316 }
1317 };
1318 }])
1319 return module;
1320}));
\No newline at end of file