UNPKG

50.6 kBJavaScriptView Raw
1/*
2 angular-file-upload v1.1.4
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.match(/[^\s,]+/g);
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 * @param {Object} headers
404 * @returns {*}
405 * @private
406 */
407 FileUploader.prototype._transformResponse = function(response, headers) {
408 var headersGetter = this._headersGetter(headers);
409 angular.forEach($http.defaults.transformResponse, function(transformFn) {
410 response = transformFn(response, headersGetter);
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 angular.forEach(headers.split('\n'), function(line) {
427 i = line.indexOf(':');
428 key = line.slice(0, i).trim().toLowerCase();
429 val = line.slice(i + 1).trim();
430
431 if (key) {
432 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
433 }
434 });
435
436 return parsed;
437 };
438 /**
439 * Returns function that returns headers
440 * @param {Object} parsedHeaders
441 * @returns {Function}
442 * @private
443 */
444 FileUploader.prototype._headersGetter = function(parsedHeaders) {
445 return function(name) {
446 if (name) {
447 return parsedHeaders[name.toLowerCase()] || null;
448 }
449 return parsedHeaders;
450 };
451 };
452 /**
453 * The XMLHttpRequest transport
454 * @param {FileItem} item
455 * @private
456 */
457 FileUploader.prototype._xhrTransport = function(item) {
458 var xhr = item._xhr = new XMLHttpRequest();
459 var form = new FormData();
460 var that = this;
461
462 that._onBeforeUploadItem(item);
463
464 angular.forEach(item.formData, function(obj) {
465 angular.forEach(obj, function(value, key) {
466 form.append(key, value);
467 });
468 });
469
470 form.append(item.alias, item._file, item.file.name);
471
472 xhr.upload.onprogress = function(event) {
473 var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
474 that._onProgressItem(item, progress);
475 };
476
477 xhr.onload = function() {
478 var headers = that._parseHeaders(xhr.getAllResponseHeaders());
479 var response = that._transformResponse(xhr.response, headers);
480 var gist = that._isSuccessCode(xhr.status) ? 'Success' : 'Error';
481 var method = '_on' + gist + 'Item';
482 that[method](item, response, xhr.status, headers);
483 that._onCompleteItem(item, response, xhr.status, headers);
484 };
485
486 xhr.onerror = function() {
487 var headers = that._parseHeaders(xhr.getAllResponseHeaders());
488 var response = that._transformResponse(xhr.response, headers);
489 that._onErrorItem(item, response, xhr.status, headers);
490 that._onCompleteItem(item, response, xhr.status, headers);
491 };
492
493 xhr.onabort = function() {
494 var headers = that._parseHeaders(xhr.getAllResponseHeaders());
495 var response = that._transformResponse(xhr.response, headers);
496 that._onCancelItem(item, response, xhr.status, headers);
497 that._onCompleteItem(item, response, xhr.status, headers);
498 };
499
500 xhr.open(item.method, item.url, true);
501
502 xhr.withCredentials = item.withCredentials;
503
504 angular.forEach(item.headers, function(value, name) {
505 xhr.setRequestHeader(name, value);
506 });
507
508 xhr.send(form);
509 this._render();
510 };
511 /**
512 * The IFrame transport
513 * @param {FileItem} item
514 * @private
515 */
516 FileUploader.prototype._iframeTransport = function(item) {
517 var form = angular.element('<form style="display: none;" />');
518 var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
519 var input = item._input;
520 var that = this;
521
522 if (item._form) item._form.replaceWith(input); // remove old form
523 item._form = form; // save link to new form
524
525 that._onBeforeUploadItem(item);
526
527 input.prop('name', item.alias);
528
529 angular.forEach(item.formData, function(obj) {
530 angular.forEach(obj, function(value, key) {
531 form.append(angular.element('<input type="hidden" name="' + key + '" value="' + value + '" />'));
532 });
533 });
534
535 form.prop({
536 action: item.url,
537 method: 'POST',
538 target: iframe.prop('name'),
539 enctype: 'multipart/form-data',
540 encoding: 'multipart/form-data' // old IE
541 });
542
543 iframe.bind('load', function() {
544 try {
545 // Fix for legacy IE browsers that loads internal error page
546 // when failed WS response received. In consequence iframe
547 // content access denied error is thrown becouse trying to
548 // access cross domain page. When such thing occurs notifying
549 // with empty response object. See more info at:
550 // http://stackoverflow.com/questions/151362/access-is-denied-error-on-accessing-iframe-document-object
551 // Note that if non standard 4xx or 5xx error code returned
552 // from WS then response content can be accessed without error
553 // but 'XHR' status becomes 200. In order to avoid confusion
554 // returning response via same 'success' event handler.
555
556 // fixed angular.contents() for iframes
557 var html = iframe[0].contentDocument.body.innerHTML;
558 } catch (e) {}
559
560 var xhr = {response: html, status: 200, dummy: true};
561 var headers = {};
562 var response = that._transformResponse(xhr.response, headers);
563
564 that._onSuccessItem(item, response, xhr.status, headers);
565 that._onCompleteItem(item, response, xhr.status, headers);
566 });
567
568 form.abort = function() {
569 var xhr = {status: 0, dummy: true};
570 var headers = {};
571 var response;
572
573 iframe.unbind('load').prop('src', 'javascript:false;');
574 form.replaceWith(input);
575
576 that._onCancelItem(item, response, xhr.status, headers);
577 that._onCompleteItem(item, response, xhr.status, headers);
578 };
579
580 input.after(form);
581 form.append(input).append(iframe);
582
583 form[0].submit();
584 this._render();
585 };
586 /**
587 * Inner callback
588 * @param {File|Object} item
589 * @param {Object} filter
590 * @param {Object} options
591 * @private
592 */
593 FileUploader.prototype._onWhenAddingFileFailed = function(item, filter, options) {
594 this.onWhenAddingFileFailed(item, filter, options);
595 };
596 /**
597 * Inner callback
598 * @param {FileItem} item
599 */
600 FileUploader.prototype._onAfterAddingFile = function(item) {
601 this.onAfterAddingFile(item);
602 };
603 /**
604 * Inner callback
605 * @param {Array<FileItem>} items
606 */
607 FileUploader.prototype._onAfterAddingAll = function(items) {
608 this.onAfterAddingAll(items);
609 };
610 /**
611 * Inner callback
612 * @param {FileItem} item
613 * @private
614 */
615 FileUploader.prototype._onBeforeUploadItem = function(item) {
616 item._onBeforeUpload();
617 this.onBeforeUploadItem(item);
618 };
619 /**
620 * Inner callback
621 * @param {FileItem} item
622 * @param {Number} progress
623 * @private
624 */
625 FileUploader.prototype._onProgressItem = function(item, progress) {
626 var total = this._getTotalProgress(progress);
627 this.progress = total;
628 item._onProgress(progress);
629 this.onProgressItem(item, progress);
630 this.onProgressAll(total);
631 this._render();
632 };
633 /**
634 * Inner callback
635 * @param {FileItem} item
636 * @param {*} response
637 * @param {Number} status
638 * @param {Object} headers
639 * @private
640 */
641 FileUploader.prototype._onSuccessItem = function(item, response, status, headers) {
642 item._onSuccess(response, status, headers);
643 this.onSuccessItem(item, response, status, headers);
644 };
645 /**
646 * Inner callback
647 * @param {FileItem} item
648 * @param {*} response
649 * @param {Number} status
650 * @param {Object} headers
651 * @private
652 */
653 FileUploader.prototype._onErrorItem = function(item, response, status, headers) {
654 item._onError(response, status, headers);
655 this.onErrorItem(item, response, status, headers);
656 };
657 /**
658 * Inner callback
659 * @param {FileItem} item
660 * @param {*} response
661 * @param {Number} status
662 * @param {Object} headers
663 * @private
664 */
665 FileUploader.prototype._onCancelItem = function(item, response, status, headers) {
666 item._onCancel(response, status, headers);
667 this.onCancelItem(item, response, status, headers);
668 };
669 /**
670 * Inner callback
671 * @param {FileItem} item
672 * @param {*} response
673 * @param {Number} status
674 * @param {Object} headers
675 * @private
676 */
677 FileUploader.prototype._onCompleteItem = function(item, response, status, headers) {
678 item._onComplete(response, status, headers);
679 this.onCompleteItem(item, response, status, headers);
680
681 var nextItem = this.getReadyItems()[0];
682 this.isUploading = false;
683
684 if(angular.isDefined(nextItem)) {
685 nextItem.upload();
686 return;
687 }
688
689 this.onCompleteAll();
690 this.progress = this._getTotalProgress();
691 this._render();
692 };
693 /**********************
694 * STATIC
695 **********************/
696 /**
697 * @borrows FileUploader.prototype.isFile
698 */
699 FileUploader.isFile = FileUploader.prototype.isFile;
700 /**
701 * @borrows FileUploader.prototype.isFileLikeObject
702 */
703 FileUploader.isFileLikeObject = FileUploader.prototype.isFileLikeObject;
704 /**
705 * @borrows FileUploader.prototype.isArrayLikeObject
706 */
707 FileUploader.isArrayLikeObject = FileUploader.prototype.isArrayLikeObject;
708 /**
709 * @borrows FileUploader.prototype.isHTML5
710 */
711 FileUploader.isHTML5 = FileUploader.prototype.isHTML5;
712 /**
713 * Inherits a target (Class_1) by a source (Class_2)
714 * @param {Function} target
715 * @param {Function} source
716 */
717 FileUploader.inherit = function(target, source) {
718 target.prototype = Object.create(source.prototype);
719 target.prototype.constructor = target;
720 target.super_ = source;
721 };
722 FileUploader.FileLikeObject = FileLikeObject;
723 FileUploader.FileItem = FileItem;
724 FileUploader.FileDirective = FileDirective;
725 FileUploader.FileSelect = FileSelect;
726 FileUploader.FileDrop = FileDrop;
727 FileUploader.FileOver = FileOver;
728
729 // ---------------------------
730
731 /**
732 * Creates an instance of FileLikeObject
733 * @param {File|HTMLInputElement|Object} fileOrInput
734 * @constructor
735 */
736 function FileLikeObject(fileOrInput) {
737 var isInput = angular.isElement(fileOrInput);
738 var fakePathOrObject = isInput ? fileOrInput.value : fileOrInput;
739 var postfix = angular.isString(fakePathOrObject) ? 'FakePath' : 'Object';
740 var method = '_createFrom' + postfix;
741 this[method](fakePathOrObject);
742 }
743
744 /**
745 * Creates file like object from fake path string
746 * @param {String} path
747 * @private
748 */
749 FileLikeObject.prototype._createFromFakePath = function(path) {
750 this.lastModifiedDate = null;
751 this.size = null;
752 this.type = 'like/' + path.slice(path.lastIndexOf('.') + 1).toLowerCase();
753 this.name = path.slice(path.lastIndexOf('/') + path.lastIndexOf('\\') + 2);
754 };
755 /**
756 * Creates file like object from object
757 * @param {File|FileLikeObject} object
758 * @private
759 */
760 FileLikeObject.prototype._createFromObject = function(object) {
761 this.lastModifiedDate = angular.copy(object.lastModifiedDate);
762 this.size = object.size;
763 this.type = object.type;
764 this.name = object.name;
765 };
766
767 // ---------------------------
768
769 /**
770 * Creates an instance of FileItem
771 * @param {FileUploader} uploader
772 * @param {File|HTMLInputElement|Object} some
773 * @param {Object} options
774 * @constructor
775 */
776 function FileItem(uploader, some, options) {
777 var isInput = angular.isElement(some);
778 var input = isInput ? angular.element(some) : null;
779 var file = !isInput ? some : null;
780
781 angular.extend(this, {
782 url: uploader.url,
783 alias: uploader.alias,
784 headers: angular.copy(uploader.headers),
785 formData: angular.copy(uploader.formData),
786 removeAfterUpload: uploader.removeAfterUpload,
787 withCredentials: uploader.withCredentials,
788 method: uploader.method
789 }, options, {
790 uploader: uploader,
791 file: new FileUploader.FileLikeObject(some),
792 isReady: false,
793 isUploading: false,
794 isUploaded: false,
795 isSuccess: false,
796 isCancel: false,
797 isError: false,
798 progress: 0,
799 index: null,
800 _file: file,
801 _input: input
802 });
803
804 if (input) this._replaceNode(input);
805 }
806 /**********************
807 * PUBLIC
808 **********************/
809 /**
810 * Uploads a FileItem
811 */
812 FileItem.prototype.upload = function() {
813 this.uploader.uploadItem(this);
814 };
815 /**
816 * Cancels uploading of FileItem
817 */
818 FileItem.prototype.cancel = function() {
819 this.uploader.cancelItem(this);
820 };
821 /**
822 * Removes a FileItem
823 */
824 FileItem.prototype.remove = function() {
825 this.uploader.removeFromQueue(this);
826 };
827 /**
828 * Callback
829 * @private
830 */
831 FileItem.prototype.onBeforeUpload = function() {};
832 /**
833 * Callback
834 * @param {Number} progress
835 * @private
836 */
837 FileItem.prototype.onProgress = function(progress) {};
838 /**
839 * Callback
840 * @param {*} response
841 * @param {Number} status
842 * @param {Object} headers
843 */
844 FileItem.prototype.onSuccess = function(response, status, headers) {};
845 /**
846 * Callback
847 * @param {*} response
848 * @param {Number} status
849 * @param {Object} headers
850 */
851 FileItem.prototype.onError = function(response, status, headers) {};
852 /**
853 * Callback
854 * @param {*} response
855 * @param {Number} status
856 * @param {Object} headers
857 */
858 FileItem.prototype.onCancel = function(response, status, headers) {};
859 /**
860 * Callback
861 * @param {*} response
862 * @param {Number} status
863 * @param {Object} headers
864 */
865 FileItem.prototype.onComplete = function(response, status, headers) {};
866 /**********************
867 * PRIVATE
868 **********************/
869 /**
870 * Inner callback
871 */
872 FileItem.prototype._onBeforeUpload = function() {
873 this.isReady = true;
874 this.isUploading = true;
875 this.isUploaded = false;
876 this.isSuccess = false;
877 this.isCancel = false;
878 this.isError = false;
879 this.progress = 0;
880 this.onBeforeUpload();
881 };
882 /**
883 * Inner callback
884 * @param {Number} progress
885 * @private
886 */
887 FileItem.prototype._onProgress = function(progress) {
888 this.progress = progress;
889 this.onProgress(progress);
890 };
891 /**
892 * Inner callback
893 * @param {*} response
894 * @param {Number} status
895 * @param {Object} headers
896 * @private
897 */
898 FileItem.prototype._onSuccess = function(response, status, headers) {
899 this.isReady = false;
900 this.isUploading = false;
901 this.isUploaded = true;
902 this.isSuccess = true;
903 this.isCancel = false;
904 this.isError = false;
905 this.progress = 100;
906 this.index = null;
907 this.onSuccess(response, status, headers);
908 };
909 /**
910 * Inner callback
911 * @param {*} response
912 * @param {Number} status
913 * @param {Object} headers
914 * @private
915 */
916 FileItem.prototype._onError = function(response, status, headers) {
917 this.isReady = false;
918 this.isUploading = false;
919 this.isUploaded = true;
920 this.isSuccess = false;
921 this.isCancel = false;
922 this.isError = true;
923 this.progress = 0;
924 this.index = null;
925 this.onError(response, status, headers);
926 };
927 /**
928 * Inner callback
929 * @param {*} response
930 * @param {Number} status
931 * @param {Object} headers
932 * @private
933 */
934 FileItem.prototype._onCancel = function(response, status, headers) {
935 this.isReady = false;
936 this.isUploading = false;
937 this.isUploaded = false;
938 this.isSuccess = false;
939 this.isCancel = true;
940 this.isError = false;
941 this.progress = 0;
942 this.index = null;
943 this.onCancel(response, status, headers);
944 };
945 /**
946 * Inner callback
947 * @param {*} response
948 * @param {Number} status
949 * @param {Object} headers
950 * @private
951 */
952 FileItem.prototype._onComplete = function(response, status, headers) {
953 this.onComplete(response, status, headers);
954 if (this.removeAfterUpload) this.remove();
955 };
956 /**
957 * Destroys a FileItem
958 */
959 FileItem.prototype._destroy = function() {
960 if (this._input) this._input.remove();
961 if (this._form) this._form.remove();
962 delete this._form;
963 delete this._input;
964 };
965 /**
966 * Prepares to uploading
967 * @private
968 */
969 FileItem.prototype._prepareToUploading = function() {
970 this.index = this.index || ++this.uploader._nextIndex;
971 this.isReady = true;
972 };
973 /**
974 * Replaces input element on his clone
975 * @param {JQLite|jQuery} input
976 * @private
977 */
978 FileItem.prototype._replaceNode = function(input) {
979 var clone = $compile(input.clone())(input.scope());
980 clone.prop('value', null); // FF fix
981 input.css('display', 'none');
982 input.after(clone); // remove jquery dependency
983 };
984
985 // ---------------------------
986
987 /**
988 * Creates instance of {FileDirective} object
989 * @param {Object} options
990 * @param {Object} options.uploader
991 * @param {HTMLElement} options.element
992 * @param {Object} options.events
993 * @param {String} options.prop
994 * @constructor
995 */
996 function FileDirective(options) {
997 angular.extend(this, options);
998 this.uploader._directives[this.prop].push(this);
999 this._saveLinks();
1000 this.bind();
1001 }
1002 /**
1003 * Map of events
1004 * @type {Object}
1005 */
1006 FileDirective.prototype.events = {};
1007 /**
1008 * Binds events handles
1009 */
1010 FileDirective.prototype.bind = function() {
1011 for(var key in this.events) {
1012 var prop = this.events[key];
1013 this.element.bind(key, this[prop]);
1014 }
1015 };
1016 /**
1017 * Unbinds events handles
1018 */
1019 FileDirective.prototype.unbind = function() {
1020 for(var key in this.events) {
1021 this.element.unbind(key, this.events[key]);
1022 }
1023 };
1024 /**
1025 * Destroys directive
1026 */
1027 FileDirective.prototype.destroy = function() {
1028 var index = this.uploader._directives[this.prop].indexOf(this);
1029 this.uploader._directives[this.prop].splice(index, 1);
1030 this.unbind();
1031 // this.element = null;
1032 };
1033 /**
1034 * Saves links to functions
1035 * @private
1036 */
1037 FileDirective.prototype._saveLinks = function() {
1038 for(var key in this.events) {
1039 var prop = this.events[key];
1040 this[prop] = this[prop].bind(this);
1041 }
1042 };
1043
1044 // ---------------------------
1045
1046 FileUploader.inherit(FileSelect, FileDirective);
1047
1048 /**
1049 * Creates instance of {FileSelect} object
1050 * @param {Object} options
1051 * @constructor
1052 */
1053 function FileSelect(options) {
1054 FileSelect.super_.apply(this, arguments);
1055
1056 if(!this.uploader.isHTML5) {
1057 this.element.removeAttr('multiple');
1058 }
1059 this.element.prop('value', null); // FF fix
1060 }
1061 /**
1062 * Map of events
1063 * @type {Object}
1064 */
1065 FileSelect.prototype.events = {
1066 $destroy: 'destroy',
1067 change: 'onChange'
1068 };
1069 /**
1070 * Name of property inside uploader._directive object
1071 * @type {String}
1072 */
1073 FileSelect.prototype.prop = 'select';
1074 /**
1075 * Returns options
1076 * @return {Object|undefined}
1077 */
1078 FileSelect.prototype.getOptions = function() {};
1079 /**
1080 * Returns filters
1081 * @return {Array<Function>|String|undefined}
1082 */
1083 FileSelect.prototype.getFilters = function() {};
1084 /**
1085 * If returns "true" then HTMLInputElement will be cleared
1086 * @returns {Boolean}
1087 */
1088 FileSelect.prototype.isEmptyAfterSelection = function() {
1089 return !!this.element.attr('multiple');
1090 };
1091 /**
1092 * Event handler
1093 */
1094 FileSelect.prototype.onChange = function() {
1095 var files = this.uploader.isHTML5 ? this.element[0].files : this.element[0];
1096 var options = this.getOptions();
1097 var filters = this.getFilters();
1098
1099 if (!this.uploader.isHTML5) this.destroy();
1100 this.uploader.addToQueue(files, options, filters);
1101 if (this.isEmptyAfterSelection()) this.element.prop('value', null);
1102 };
1103
1104 // ---------------------------
1105
1106 FileUploader.inherit(FileDrop, FileDirective);
1107
1108 /**
1109 * Creates instance of {FileDrop} object
1110 * @param {Object} options
1111 * @constructor
1112 */
1113 function FileDrop(options) {
1114 FileDrop.super_.apply(this, arguments);
1115 }
1116 /**
1117 * Map of events
1118 * @type {Object}
1119 */
1120 FileDrop.prototype.events = {
1121 $destroy: 'destroy',
1122 drop: 'onDrop',
1123 dragover: 'onDragOver',
1124 dragleave: 'onDragLeave'
1125 };
1126 /**
1127 * Name of property inside uploader._directive object
1128 * @type {String}
1129 */
1130 FileDrop.prototype.prop = 'drop';
1131 /**
1132 * Returns options
1133 * @return {Object|undefined}
1134 */
1135 FileDrop.prototype.getOptions = function() {};
1136 /**
1137 * Returns filters
1138 * @return {Array<Function>|String|undefined}
1139 */
1140 FileDrop.prototype.getFilters = function() {};
1141 /**
1142 * Event handler
1143 */
1144 FileDrop.prototype.onDrop = function(event) {
1145 var transfer = this._getTransfer(event);
1146 if (!transfer) return;
1147 var options = this.getOptions();
1148 var filters = this.getFilters();
1149 this._preventAndStop(event);
1150 angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
1151 this.uploader.addToQueue(transfer.files, options, filters);
1152 };
1153 /**
1154 * Event handler
1155 */
1156 FileDrop.prototype.onDragOver = function(event) {
1157 var transfer = this._getTransfer(event);
1158 if(!this._haveFiles(transfer.types)) return;
1159 transfer.dropEffect = 'copy';
1160 this._preventAndStop(event);
1161 angular.forEach(this.uploader._directives.over, this._addOverClass, this);
1162 };
1163 /**
1164 * Event handler
1165 */
1166 FileDrop.prototype.onDragLeave = function(event) {
1167 if (event.currentTarget !== this.element[0]) return;
1168 this._preventAndStop(event);
1169 angular.forEach(this.uploader._directives.over, this._removeOverClass, this);
1170 };
1171 /**
1172 * Helper
1173 */
1174 FileDrop.prototype._getTransfer = function(event) {
1175 return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix;
1176 };
1177 /**
1178 * Helper
1179 */
1180 FileDrop.prototype._preventAndStop = function(event) {
1181 event.preventDefault();
1182 event.stopPropagation();
1183 };
1184 /**
1185 * Returns "true" if types contains files
1186 * @param {Object} types
1187 */
1188 FileDrop.prototype._haveFiles = function(types) {
1189 if (!types) return false;
1190 if (types.indexOf) {
1191 return types.indexOf('Files') !== -1;
1192 } else if(types.contains) {
1193 return types.contains('Files');
1194 } else {
1195 return false;
1196 }
1197 };
1198 /**
1199 * Callback
1200 */
1201 FileDrop.prototype._addOverClass = function(item) {
1202 item.addOverClass();
1203 };
1204 /**
1205 * Callback
1206 */
1207 FileDrop.prototype._removeOverClass = function(item) {
1208 item.removeOverClass();
1209 };
1210
1211 // ---------------------------
1212
1213 FileUploader.inherit(FileOver, FileDirective);
1214
1215 /**
1216 * Creates instance of {FileDrop} object
1217 * @param {Object} options
1218 * @constructor
1219 */
1220 function FileOver(options) {
1221 FileOver.super_.apply(this, arguments);
1222 }
1223 /**
1224 * Map of events
1225 * @type {Object}
1226 */
1227 FileOver.prototype.events = {
1228 $destroy: 'destroy'
1229 };
1230 /**
1231 * Name of property inside uploader._directive object
1232 * @type {String}
1233 */
1234 FileOver.prototype.prop = 'over';
1235 /**
1236 * Over class
1237 * @type {string}
1238 */
1239 FileOver.prototype.overClass = 'nv-file-over';
1240 /**
1241 * Adds over class
1242 */
1243 FileOver.prototype.addOverClass = function() {
1244 this.element.addClass(this.getOverClass());
1245 };
1246 /**
1247 * Removes over class
1248 */
1249 FileOver.prototype.removeOverClass = function() {
1250 this.element.removeClass(this.getOverClass());
1251 };
1252 /**
1253 * Returns over class
1254 * @returns {String}
1255 */
1256 FileOver.prototype.getOverClass = function() {
1257 return this.overClass;
1258 };
1259
1260 return FileUploader;
1261 }])
1262
1263
1264 .directive('nvFileSelect', ['$parse', 'FileUploader', function($parse, FileUploader) {
1265 return {
1266 link: function(scope, element, attributes) {
1267 var uploader = scope.$eval(attributes.uploader);
1268
1269 if (!(uploader instanceof FileUploader)) {
1270 throw new TypeError('"Uploader" must be an instance of FileUploader');
1271 }
1272
1273 var object = new FileUploader.FileSelect({
1274 uploader: uploader,
1275 element: element
1276 });
1277
1278 object.getOptions = $parse(attributes.options).bind(object, scope);
1279 object.getFilters = function() {return attributes.filters;};
1280 }
1281 };
1282 }])
1283
1284
1285 .directive('nvFileDrop', ['$parse', 'FileUploader', function($parse, FileUploader) {
1286 return {
1287 link: function(scope, element, attributes) {
1288 var uploader = scope.$eval(attributes.uploader);
1289
1290 if (!(uploader instanceof FileUploader)) {
1291 throw new TypeError('"Uploader" must be an instance of FileUploader');
1292 }
1293
1294 if (!uploader.isHTML5) return;
1295
1296 var object = new FileUploader.FileDrop({
1297 uploader: uploader,
1298 element: element
1299 });
1300
1301 object.getOptions = $parse(attributes.options).bind(object, scope);
1302 object.getFilters = function() {return attributes.filters;};
1303 }
1304 };
1305 }])
1306
1307
1308 .directive('nvFileOver', ['FileUploader', function(FileUploader) {
1309 return {
1310 link: function(scope, element, attributes) {
1311 var uploader = scope.$eval(attributes.uploader);
1312
1313 if (!(uploader instanceof FileUploader)) {
1314 throw new TypeError('"Uploader" must be an instance of FileUploader');
1315 }
1316
1317 var object = new FileUploader.FileOver({
1318 uploader: uploader,
1319 element: element
1320 });
1321
1322 object.getOverClass = function() {
1323 return attributes.overClass || this.overClass;
1324 };
1325 }
1326 };
1327 }])
1328
1329 return module;
1330}));
\No newline at end of file