pyc-website
main website for pyc inc.
git clone https://9o.is/git/pyc-website.git
angular-file-upload.js
(22368B)
1 /*
2 angular-file-upload v0.5.7
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 }(angular || null, function(angular) {
14 var app = angular.module('angularFileUpload', []);
15
16 // It is attached to an element that catches the event drop file
17 app.directive('ngFileDrop', ['$fileUploader', function ($fileUploader) {
18 'use strict';
19
20 return {
21 // don't use drag-n-drop files in IE9, because not File API support
22 link: !$fileUploader.isHTML5 ? angular.noop : function (scope, element, attributes) {
23 element
24 .bind('drop', function (event) {
25 var dataTransfer = event.dataTransfer ?
26 event.dataTransfer :
27 event.originalEvent.dataTransfer; // jQuery fix;
28 if (!dataTransfer) return;
29 event.preventDefault();
30 event.stopPropagation();
31 scope.$broadcast('file:removeoverclass');
32 scope.$emit('file:add', dataTransfer.files, scope.$eval(attributes.ngFileDrop));
33 })
34 .bind('dragover', function (event) {
35 var dataTransfer = event.dataTransfer ?
36 event.dataTransfer :
37 event.originalEvent.dataTransfer; // jQuery fix;
38
39 event.preventDefault();
40 event.stopPropagation();
41 dataTransfer.dropEffect = 'copy';
42 scope.$broadcast('file:addoverclass');
43 })
44 .bind('dragleave', function (event) {
45 if (event.target === element[0]) {
46 scope.$broadcast('file:removeoverclass');
47 }
48 });
49 }
50 };
51 }])
52
53 // It is attached to an element which will be assigned to a class "ng-file-over" or ng-file-over="className"
54 app.directive('ngFileOver', function () {
55 'use strict';
56
57 return {
58 link: function (scope, element, attributes) {
59 scope.$on('file:addoverclass', function () {
60 element.addClass(attributes.ngFileOver || 'ng-file-over');
61 });
62 scope.$on('file:removeoverclass', function () {
63 element.removeClass(attributes.ngFileOver || 'ng-file-over');
64 });
65 }
66 };
67 });
68 // It is attached to <input type="file"> element like <ng-file-select="options">
69 app.directive('ngFileSelect', ['$fileUploader', function($fileUploader) {
70 'use strict';
71
72 return {
73 link: function(scope, element, attributes) {
74 if(!$fileUploader.isHTML5) {
75 element.removeAttr('multiple');
76 }
77
78 element.bind('change', function() {
79 var data = $fileUploader.isHTML5 ? this.files : this;
80 var options = scope.$eval(attributes.ngFileSelect);
81
82 scope.$emit('file:add', data, options);
83
84 if($fileUploader.isHTML5 && element.attr('multiple')) {
85 element.prop('value', null);
86 }
87 });
88
89 element.prop('value', null); // FF fix
90 }
91 };
92 }]);
93 app.factory('$fileUploader', ['$compile', '$rootScope', '$http', '$window', function($compile, $rootScope, $http, $window) {
94 'use strict';
95
96 /**
97 * Creates a uploader
98 * @param {Object} params
99 * @constructor
100 */
101 function Uploader(params) {
102 angular.extend(this, {
103 scope: $rootScope,
104 url: '/',
105 alias: 'file',
106 queue: [],
107 headers: {},
108 progress: null,
109 autoUpload: false,
110 removeAfterUpload: false,
111 method: 'POST',
112 filters: [],
113 formData: [],
114 isUploading: false,
115 queueLimit: Number.MAX_VALUE,
116 withCredentials: false,
117 _nextIndex: 0,
118 _timestamp: Date.now()
119 }, params);
120
121 // add default filters
122 this.filters.unshift(this._queueLimitFilter);
123 this.filters.unshift(this._emptyFileFilter);
124
125 this.scope.$on('file:add', function(event, items, options) {
126 event.stopPropagation();
127 this.addToQueue(items, options);
128 }.bind(this));
129
130 this.bind('beforeupload', Item.prototype._beforeupload);
131 this.bind('in:progress', Item.prototype._progress);
132 this.bind('in:success', Item.prototype._success);
133 this.bind('in:cancel', Item.prototype._cancel);
134 this.bind('in:error', Item.prototype._error);
135 this.bind('in:complete', Item.prototype._complete);
136 this.bind('in:progress', this._progress);
137 this.bind('in:complete', this._complete);
138 }
139
140 Uploader.prototype = {
141 /**
142 * Link to the constructor
143 */
144 constructor: Uploader,
145
146 /**
147 * Returns "true" if item is DOMElement or a file with size > 0
148 * @param {File|Input} item
149 * @returns {Boolean}
150 * @private
151 */
152 _emptyFileFilter: function(item) {
153 return angular.isElement(item) ? true : !!item.size;
154 },
155
156 /**
157 * Returns "true" if the limit has not been reached
158 * @returns {Boolean}
159 * @private
160 */
161 _queueLimitFilter: function() {
162 return this.queue.length < this.queueLimit;
163 },
164
165 /**
166 * Registers a event handler
167 * @param {String} event
168 * @param {Function} handler
169 * @return {Function} unsubscribe function
170 */
171 bind: function(event, handler) {
172 return this.scope.$on(this._timestamp + ':' + event, handler.bind(this));
173 },
174
175 /**
176 * Triggers events
177 * @param {String} event
178 * @param {...*} [some]
179 */
180 trigger: function(event, some) {
181 arguments[0] = this._timestamp + ':' + event;
182 this.scope.$broadcast.apply(this.scope, arguments);
183 },
184
185 /**
186 * Checks a support the html5 uploader
187 * @returns {Boolean}
188 * @readonly
189 */
190 isHTML5: !!($window.File && $window.FormData),
191
192 /**
193 * Adds items to the queue
194 * @param {FileList|File|HTMLInputElement} items
195 * @param {Object} [options]
196 */
197 addToQueue: function(items, options) {
198 var length = this.queue.length;
199 var list = 'length' in items ? items : [items];
200
201 angular.forEach(list, function(file) {
202 // check a [File|HTMLInputElement]
203 var isValid = !this.filters.length ? true : this.filters.every(function(filter) {
204 return filter.call(this, file);
205 }, this);
206
207 // create new item
208 var item = new Item(angular.extend({
209 url: this.url,
210 alias: this.alias,
211 headers: angular.copy(this.headers),
212 formData: angular.copy(this.formData),
213 removeAfterUpload: this.removeAfterUpload,
214 withCredentials: this.withCredentials,
215 method: this.method,
216 uploader: this,
217 file: file
218 }, options));
219
220 if(isValid) {
221 this.queue.push(item);
222 this.trigger('afteraddingfile', item);
223 } else {
224 this.trigger('whenaddingfilefailed', item);
225 }
226 }, this);
227
228 if(this.queue.length !== length) {
229 this.trigger('afteraddingall', this.queue);
230 this.progress = this._getTotalProgress();
231 }
232
233 this._render();
234 this.autoUpload && this.uploadAll();
235 },
236
237 /**
238 * Remove items from the queue. Remove last: index = -1
239 * @param {Item|Number} value
240 */
241 removeFromQueue: function(value) {
242 var index = this.getIndexOfItem(value);
243 var item = this.queue[index];
244 if (item.cancel) item.cancel();
245 if (item._destroy) item._destroy();
246 this.queue.splice(index, 1);
247 this.progress = this._getTotalProgress();
248 },
249
250 /**
251 * Clears the queue
252 */
253 clearQueue: function() {
254 while(this.queue.length) {
255 this.queue[this.queue.length - 1].remove();
256 }
257 },
258
259 /**
260 * Uploads a item from the queue
261 * @param {Item|Number} value
262 */
263 uploadItem: function(value) {
264 var index = this.getIndexOfItem(value);
265 var item = this.queue[index];
266 var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport';
267
268 item.index = item.index || this._nextIndex++;
269 item.isReady = true;
270
271 if(this.isUploading) return;
272
273 this.isUploading = true;
274 this[transport](item);
275 },
276
277 /**
278 * Cancels uploading of item from the queue
279 * @param {Item|Number} value
280 */
281 cancelItem: function(value) {
282 var index = this.getIndexOfItem(value);
283 var item = this.queue[index];
284 var prop = this.isHTML5 ? '_xhr' : '_form';
285 if (item[prop]) item[prop].abort();
286 },
287
288 /**
289 * Uploads all not uploaded items of queue
290 */
291 uploadAll: function() {
292 var items = this.getNotUploadedItems().filter(function(item) {
293 return !item.isUploading;
294 });
295 items.forEach(function(item) {
296 item.index = item.index || this._nextIndex++;
297 item.isReady = true;
298 }, this);
299 items.length && this.uploadItem(items[0]);
300 },
301
302 /**
303 * Cancels all uploads
304 */
305 cancelAll: function() {
306 this.getNotUploadedItems().forEach(function(item) {
307 this.cancelItem(item);
308 }, this);
309 },
310
311 /**
312 * Returns a index of item from the queue
313 * @param {Item|Number} value
314 * @returns {Number}
315 */
316 getIndexOfItem: function(value) {
317 return angular.isNumber(value) ? value : this.queue.indexOf(value);
318 },
319
320 /**
321 * Returns not uploaded items
322 * @returns {Array}
323 */
324 getNotUploadedItems: function() {
325 return this.queue.filter(function(item) {
326 return !item.isUploaded;
327 });
328 },
329
330 /**
331 * Returns items ready for upload
332 * @returns {Array}
333 */
334 getReadyItems: function() {
335 return this.queue
336 .filter(function(item) {
337 return item.isReady && !item.isUploading;
338 })
339 .sort(function(item1, item2) {
340 return item1.index - item2.index;
341 });
342 },
343
344 /**
345 * Updates angular scope
346 * @private
347 */
348 _render: function() {
349 if (!this.scope.$$phase) this.scope.$digest();
350 },
351
352 /**
353 * Returns the total progress
354 * @param {Number} [value]
355 * @returns {Number}
356 * @private
357 */
358 _getTotalProgress: function(value) {
359 if(this.removeAfterUpload) {
360 return value || 0;
361 }
362
363 var notUploaded = this.getNotUploadedItems().length;
364 var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length;
365 var ratio = 100 / this.queue.length;
366 var current = (value || 0) * ratio / 100;
367
368 return Math.round(uploaded * ratio + current);
369 },
370
371 /**
372 * The 'in:progress' handler
373 * @private
374 */
375 _progress: function(event, item, progress) {
376 var result = this._getTotalProgress(progress);
377 this.trigger('progressall', result);
378 this.progress = result;
379 this._render();
380 },
381
382 /**
383 * The 'in:complete' handler
384 * @private
385 */
386 _complete: function() {
387 var item = this.getReadyItems()[0];
388 this.isUploading = false;
389
390 if(angular.isDefined(item)) {
391 this.uploadItem(item);
392 return;
393 }
394
395 this.trigger('completeall', this.queue);
396 this.progress = this._getTotalProgress();
397 this._render();
398 },
399
400 /**
401 * The XMLHttpRequest transport
402 * @private
403 */
404 _xhrTransport: function(item) {
405 var xhr = item._xhr = new XMLHttpRequest();
406 var form = new FormData();
407 var that = this;
408
409 this.trigger('beforeupload', item);
410
411 item.formData.forEach(function(obj) {
412 angular.forEach(obj, function(value, key) {
413 form.append(key, value);
414 });
415 });
416
417 form.append(item.alias, item.file);
418
419 xhr.upload.onprogress = function(event) {
420 var progress = event.lengthComputable ? event.loaded * 100 / event.total : 0;
421 that.trigger('in:progress', item, Math.round(progress));
422 };
423
424 xhr.onload = function() {
425 var response = that._transformResponse(xhr.response);
426 var event = that._isSuccessCode(xhr.status) ? 'success' : 'error';
427 that.trigger('in:' + event, xhr, item, response);
428 that.trigger('in:complete', xhr, item, response);
429 };
430
431 xhr.onerror = function() {
432 that.trigger('in:error', xhr, item);
433 that.trigger('in:complete', xhr, item);
434 };
435
436 xhr.onabort = function() {
437 that.trigger('in:cancel', xhr, item);
438 that.trigger('in:complete', xhr, item);
439 };
440
441 xhr.open(item.method, item.url, true);
442
443 xhr.withCredentials = item.withCredentials;
444
445 angular.forEach(item.headers, function(value, name) {
446 xhr.setRequestHeader(name, value);
447 });
448
449 xhr.send(form);
450 },
451
452 /**
453 * The IFrame transport
454 * @private
455 */
456 _iframeTransport: function(item) {
457 var form = angular.element('<form style="display: none;" />');
458 var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
459 var input = item._input;
460 var that = this;
461
462 if (item._form) item._form.replaceWith(input); // remove old form
463 item._form = form; // save link to new form
464
465 this.trigger('beforeupload', item);
466
467 input.prop('name', item.alias);
468
469 item.formData.forEach(function(obj) {
470 angular.forEach(obj, function(value, key) {
471 form.append(angular.element('<input type="hidden" name="' + key + '" value="' + value + '" />'));
472 });
473 });
474
475 form.prop({
476 action: item.url,
477 method: 'POST',
478 target: iframe.prop('name'),
479 enctype: 'multipart/form-data',
480 encoding: 'multipart/form-data' // old IE
481 });
482
483 iframe.bind('load', function() {
484 // fixed angular.contents() for iframes
485 var html = iframe[0].contentDocument.body.innerHTML;
486 var xhr = {response: html, status: 200, dummy: true};
487 var response = that._transformResponse(xhr.response);
488 that.trigger('in:success', xhr, item, response);
489 that.trigger('in:complete', xhr, item, response);
490 });
491
492 form.abort = function() {
493 var xhr = {status: 0, dummy: true};
494 iframe.unbind('load').prop('src', 'javascript:false;');
495 form.replaceWith(input);
496 that.trigger('in:cancel', xhr, item);
497 that.trigger('in:complete', xhr, item);
498 };
499
500 input.after(form);
501 form.append(input).append(iframe);
502
503 form[0].submit();
504 },
505
506 /**
507 * Checks whether upload successful
508 * @param {Number} status
509 * @returns {Boolean}
510 * @private
511 */
512 _isSuccessCode: function(status) {
513 return (status >= 200 && status < 300) || status === 304;
514 },
515
516 /**
517 * Transforms the server response
518 * @param {*} response
519 * @returns {*}
520 * @private
521 */
522 _transformResponse: function(response) {
523 $http.defaults.transformResponse.forEach(function(transformFn) {
524 response = transformFn(response);
525 });
526 return response;
527 }
528 };
529
530
531 /**
532 * Create a item
533 * @param {Object} [params]
534 * @constructor
535 */
536 function Item(params) {
537 // fix for old browsers
538 if(!Uploader.prototype.isHTML5) {
539 var input = angular.element(params.file);
540 var clone = $compile(input.clone())(params.uploader.scope);
541 var value = input.val();
542
543 params.file = {
544 lastModifiedDate: null,
545 size: null,
546 type: 'like/' + value.slice(value.lastIndexOf('.') + 1).toLowerCase(),
547 name: value.slice(value.lastIndexOf('/') + value.lastIndexOf('\\') + 2)
548 };
549
550 params._input = input;
551 clone.prop('value', null); // FF fix
552 input.css('display', 'none').after(clone); // remove jquery dependency
553 }
554
555 angular.extend(this, {
556 isReady: false,
557 isUploading: false,
558 isUploaded: false,
559 isSuccess: false,
560 isCancel: false,
561 isError: false,
562 progress: null,
563 index: null
564 }, params);
565 }
566
567
568 Item.prototype = {
569 /**
570 * Link to the constructor
571 */
572 constructor: Item,
573 /**
574 * Removes a item
575 */
576 remove: function() {
577 this.uploader.removeFromQueue(this);
578 },
579 /**
580 * Uploads a item
581 */
582 upload: function() {
583 this.uploader.uploadItem(this);
584 },
585 /**
586 * Cancels uploading
587 */
588 cancel: function() {
589 this.uploader.cancelItem(this);
590 },
591 /**
592 * Destroys form and input
593 * @private
594 */
595 _destroy: function() {
596 if (this._form) this._form.remove();
597 if (this._input) this._input.remove();
598 delete this._form;
599 delete this._input;
600 },
601 /**
602 * The 'beforeupload' handler
603 * @param {Object} event
604 * @param {Item} item
605 * @private
606 */
607 _beforeupload: function(event, item) {
608 item.isReady = true;
609 item.isUploading = true;
610 item.isUploaded = false;
611 item.isSuccess = false;
612 item.isCancel = false;
613 item.isError = false;
614 item.progress = 0;
615 },
616 /**
617 * The 'in:progress' handler
618 * @param {Object} event
619 * @param {Item} item
620 * @param {Number} progress
621 * @private
622 */
623 _progress: function(event, item, progress) {
624 item.progress = progress;
625 item.uploader.trigger('progress', item, progress);
626 },
627 /**
628 * The 'in:success' handler
629 * @param {Object} event
630 * @param {XMLHttpRequest} xhr
631 * @param {Item} item
632 * @param {*} response
633 * @private
634 */
635 _success: function(event, xhr, item, response) {
636 item.isReady = false;
637 item.isUploading = false;
638 item.isUploaded = true;
639 item.isSuccess = true;
640 item.isCancel = false;
641 item.isError = false;
642 item.progress = 100;
643 item.index = null;
644 item.uploader.trigger('success', xhr, item, response);
645 },
646 /**
647 * The 'in:cancel' handler
648 * @param {Object} event
649 * @param {XMLHttpRequest} xhr
650 * @param {Item} item
651 * @private
652 */
653 _cancel: function(event, xhr, item) {
654 item.isReady = false;
655 item.isUploading = false;
656 item.isUploaded = false;
657 item.isSuccess = false;
658 item.isCancel = true;
659 item.isError = false;
660 item.progress = 0;
661 item.index = null;
662 item.uploader.trigger('cancel', xhr, item);
663 },
664 /**
665 * The 'in:error' handler
666 * @param {Object} event
667 * @param {XMLHttpRequest} xhr
668 * @param {Item} item
669 * @param {*} response
670 * @private
671 */
672 _error: function(event, xhr, item, response) {
673 item.isReady = false;
674 item.isUploading = false;
675 item.isUploaded = true;
676 item.isSuccess = false;
677 item.isCancel = false;
678 item.isError = true;
679 item.progress = 100;
680 item.index = null;
681 item.uploader.trigger('error', xhr, item, response);
682 },
683 /**
684 * The 'in:complete' handler
685 * @param {Object} event
686 * @param {XMLHttpRequest} xhr
687 * @param {Item} item
688 * @param {*} response
689 * @private
690 */
691 _complete: function(event, xhr, item, response) {
692 item.uploader.trigger('complete', xhr, item, response);
693 item.removeAfterUpload && item.remove();
694 }
695 };
696
697 return {
698 create: function(params) {
699 return new Uploader(params);
700 },
701 isHTML5: Uploader.prototype.isHTML5
702 };
703 }])
704
705 return app;
706 }));