diff --git a/ajax/libs/angular-file-upload/0.5.6/angular-file-upload.js b/ajax/libs/angular-file-upload/0.5.6/angular-file-upload.js new file mode 100644 index 000000000..b9971d478 --- /dev/null +++ b/ajax/libs/angular-file-upload/0.5.6/angular-file-upload.js @@ -0,0 +1,703 @@ +/* + Angular File Upload v0.5.6 + https://github.com/nervgh/angular-file-upload +*/ +(function(angular, factory) { + if (typeof define === 'function' && define.amd) { + define('angular-file-upload', ['angular'], function(angular) { + return factory(angular); + }); + } else { + return factory(angular); + } +}(angular || null, function(angular) { +var app = angular.module('angularFileUpload', []); + +// It is attached to an element that catches the event drop file +app.directive('ngFileDrop', ['$fileUploader', function ($fileUploader) { + 'use strict'; + + return { + // don't use drag-n-drop files in IE9, because not File API support + link: !$fileUploader.isHTML5 ? angular.noop : function (scope, element, attributes) { + element + .bind('drop', function (event) { + var dataTransfer = event.dataTransfer ? + event.dataTransfer : + event.originalEvent.dataTransfer; // jQuery fix; + if (!dataTransfer) return; + event.preventDefault(); + event.stopPropagation(); + scope.$broadcast('file:removeoverclass'); + scope.$emit('file:add', dataTransfer.files, scope.$eval(attributes.ngFileDrop)); + }) + .bind('dragover', function (event) { + var dataTransfer = event.dataTransfer ? + event.dataTransfer : + event.originalEvent.dataTransfer; // jQuery fix; + + event.preventDefault(); + event.stopPropagation(); + dataTransfer.dropEffect = 'copy'; + scope.$broadcast('file:addoverclass'); + }) + .bind('dragleave', function () { + scope.$broadcast('file:removeoverclass'); + }); + } + }; +}]) +// It is attached to an element which will be assigned to a class "ng-file-over" or ng-file-over="className" +app.directive('ngFileOver', function () { + 'use strict'; + + return { + link: function (scope, element, attributes) { + scope.$on('file:addoverclass', function () { + element.addClass(attributes.ngFileOver || 'ng-file-over'); + }); + scope.$on('file:removeoverclass', function () { + element.removeClass(attributes.ngFileOver || 'ng-file-over'); + }); + } + }; +}); +// It is attached to element like +app.directive('ngFileSelect', ['$fileUploader', function($fileUploader) { + 'use strict'; + + return { + link: function(scope, element, attributes) { + if(!$fileUploader.isHTML5) { + element.removeAttr('multiple'); + } + + element.bind('change', function() { + var data = $fileUploader.isHTML5 ? this.files : this; + var options = scope.$eval(attributes.ngFileSelect); + + scope.$emit('file:add', data, options); + + if($fileUploader.isHTML5 && element.attr('multiple')) { + element.prop('value', null); + } + }); + + element.prop('value', null); // FF fix + } + }; +}]); +app.factory('$fileUploader', ['$compile', '$rootScope', '$http', '$window', function($compile, $rootScope, $http, $window) { + 'use strict'; + + /** + * Creates a uploader + * @param {Object} params + * @constructor + */ + function Uploader(params) { + angular.extend(this, { + scope: $rootScope, + url: '/', + alias: 'file', + queue: [], + headers: {}, + progress: null, + autoUpload: false, + removeAfterUpload: false, + method: 'POST', + filters: [], + formData: [], + isUploading: false, + queueLimit: Number.MAX_VALUE, + withCredentials: false, + _nextIndex: 0, + _timestamp: Date.now() + }, params); + + // add default filters + this.filters.unshift(this._queueLimitFilter); + this.filters.unshift(this._emptyFileFilter); + + this.scope.$on('file:add', function(event, items, options) { + event.stopPropagation(); + this.addToQueue(items, options); + }.bind(this)); + + this.bind('beforeupload', Item.prototype._beforeupload); + this.bind('in:progress', Item.prototype._progress); + this.bind('in:success', Item.prototype._success); + this.bind('in:cancel', Item.prototype._cancel); + this.bind('in:error', Item.prototype._error); + this.bind('in:complete', Item.prototype._complete); + this.bind('in:progress', this._progress); + this.bind('in:complete', this._complete); + } + + Uploader.prototype = { + /** + * Link to the constructor + */ + constructor: Uploader, + + /** + * Returns "true" if item is DOMElement or a file with size > 0 + * @param {File|Input} item + * @returns {Boolean} + * @private + */ + _emptyFileFilter: function(item) { + return angular.isElement(item) ? true : !!item.size; + }, + + /** + * Returns "true" if the limit has not been reached + * @returns {Boolean} + * @private + */ + _queueLimitFilter: function() { + return this.queue.length < this.queueLimit; + }, + + /** + * Registers a event handler + * @param {String} event + * @param {Function} handler + * @return {Function} unsubscribe function + */ + bind: function(event, handler) { + return this.scope.$on(this._timestamp + ':' + event, handler.bind(this)); + }, + + /** + * Triggers events + * @param {String} event + * @param {...*} [some] + */ + trigger: function(event, some) { + arguments[0] = this._timestamp + ':' + event; + this.scope.$broadcast.apply(this.scope, arguments); + }, + + /** + * Checks a support the html5 uploader + * @returns {Boolean} + * @readonly + */ + isHTML5: !!($window.File && $window.FormData), + + /** + * Adds items to the queue + * @param {FileList|File|HTMLInputElement} items + * @param {Object} [options] + */ + addToQueue: function(items, options) { + var length = this.queue.length; + var list = 'length' in items ? items : [items]; + + angular.forEach(list, function(file) { + // check a [File|HTMLInputElement] + var isValid = !this.filters.length ? true : this.filters.every(function(filter) { + return filter.call(this, file); + }, this); + + // create new item + var item = new Item(angular.extend({ + url: this.url, + alias: this.alias, + headers: angular.copy(this.headers), + formData: angular.copy(this.formData), + removeAfterUpload: this.removeAfterUpload, + withCredentials: this.withCredentials, + method: this.method, + uploader: this, + file: file + }, options)); + + if(isValid) { + this.queue.push(item); + this.trigger('afteraddingfile', item); + } else { + this.trigger('whenaddingfilefailed', item); + } + }, this); + + if(this.queue.length !== length) { + this.trigger('afteraddingall', this.queue); + this.progress = this._getTotalProgress(); + } + + this._render(); + this.autoUpload && this.uploadAll(); + }, + + /** + * Remove items from the queue. Remove last: index = -1 + * @param {Item|Number} value + */ + removeFromQueue: function(value) { + var index = this.getIndexOfItem(value); + var item = this.queue[index]; + if (item.cancel) item.cancel(); + if (item._destroy) item._destroy(); + this.queue.splice(index, 1); + this.progress = this._getTotalProgress(); + }, + + /** + * Clears the queue + */ + clearQueue: function() { + while(this.queue.length) { + this.queue[this.queue.length - 1].remove(); + } + }, + + /** + * Uploads a item from the queue + * @param {Item|Number} value + */ + uploadItem: function(value) { + var index = this.getIndexOfItem(value); + var item = this.queue[index]; + var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport'; + + item.index = item.index || this._nextIndex++; + item.isReady = true; + + if(this.isUploading) return; + + this.isUploading = true; + this[transport](item); + }, + + /** + * Cancels uploading of item from the queue + * @param {Item|Number} value + */ + cancelItem: function(value) { + var index = this.getIndexOfItem(value); + var item = this.queue[index]; + var prop = this.isHTML5 ? '_xhr' : '_form'; + if (item[prop]) item[prop].abort(); + }, + + /** + * Uploads all not uploaded items of queue + */ + uploadAll: function() { + var items = this.getNotUploadedItems().filter(function(item) { + return !item.isUploading; + }); + items.forEach(function(item) { + item.index = item.index || this._nextIndex++; + item.isReady = true; + }, this); + items.length && this.uploadItem(items[0]); + }, + + /** + * Cancels all uploads + */ + cancelAll: function() { + this.getNotUploadedItems().forEach(function(item) { + this.cancelItem(item); + }, this); + }, + + /** + * Returns a index of item from the queue + * @param {Item|Number} value + * @returns {Number} + */ + getIndexOfItem: function(value) { + return angular.isNumber(value) ? value : this.queue.indexOf(value); + }, + + /** + * Returns not uploaded items + * @returns {Array} + */ + getNotUploadedItems: function() { + return this.queue.filter(function(item) { + return !item.isUploaded; + }); + }, + + /** + * Returns items ready for upload + * @returns {Array} + */ + getReadyItems: function() { + return this.queue + .filter(function(item) { + return item.isReady && !item.isUploading; + }) + .sort(function(item1, item2) { + return item1.index - item2.index; + }); + }, + + /** + * Updates angular scope + * @private + */ + _render: function() { + if (!this.scope.$$phase) this.scope.$digest(); + }, + + /** + * Returns the total progress + * @param {Number} [value] + * @returns {Number} + * @private + */ + _getTotalProgress: function(value) { + if(this.removeAfterUpload) { + return value || 0; + } + + var notUploaded = this.getNotUploadedItems().length; + var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length; + var ratio = 100 / this.queue.length; + var current = (value || 0) * ratio / 100; + + return Math.round(uploaded * ratio + current); + }, + + /** + * The 'in:progress' handler + * @private + */ + _progress: function(event, item, progress) { + var result = this._getTotalProgress(progress); + this.trigger('progressall', result); + this.progress = result; + this._render(); + }, + + /** + * The 'in:complete' handler + * @private + */ + _complete: function() { + var item = this.getReadyItems()[0]; + this.isUploading = false; + + if(angular.isDefined(item)) { + this.uploadItem(item); + return; + } + + this.trigger('completeall', this.queue); + this.progress = this._getTotalProgress(); + this._render(); + }, + + /** + * The XMLHttpRequest transport + * @private + */ + _xhrTransport: function(item) { + var xhr = item._xhr = new XMLHttpRequest(); + var form = new FormData(); + var that = this; + + this.trigger('beforeupload', item); + + item.formData.forEach(function(obj) { + angular.forEach(obj, function(value, key) { + form.append(key, value); + }); + }); + + form.append(item.alias, item.file); + + xhr.upload.onprogress = function(event) { + var progress = event.lengthComputable ? event.loaded * 100 / event.total : 0; + that.trigger('in:progress', item, Math.round(progress)); + }; + + xhr.onload = function() { + var response = that._transformResponse(xhr.response); + var event = that._isSuccessCode(xhr.status) ? 'success' : 'error'; + that.trigger('in:' + event, xhr, item, response); + that.trigger('in:complete', xhr, item, response); + }; + + xhr.onerror = function() { + that.trigger('in:error', xhr, item); + that.trigger('in:complete', xhr, item); + }; + + xhr.onabort = function() { + that.trigger('in:cancel', xhr, item); + that.trigger('in:complete', xhr, item); + }; + + xhr.open(item.method, item.url, true); + + xhr.withCredentials = item.withCredentials; + + angular.forEach(item.headers, function(value, name) { + xhr.setRequestHeader(name, value); + }); + + xhr.send(form); + }, + + /** + * The IFrame transport + * @private + */ + _iframeTransport: function(item) { + var form = angular.element('
'); + var iframe = angular.element('