/* angular-file-upload v1.1.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); } }(typeof angular === 'undefined' ? null : angular, function(angular) { var module = angular.module('angularFileUpload', []); 'use strict'; /** * Classes * * FileUploader * FileUploader.FileLikeObject * FileUploader.FileItem * FileUploader.FileDirective * FileUploader.FileSelect * FileUploader.FileDrop * FileUploader.FileOver */ module .value('fileUploaderOptions', { url: '/', alias: 'file', headers: {}, queue: [], progress: 0, autoUpload: false, removeAfterUpload: false, method: 'POST', filters: [], formData: [], queueLimit: Number.MAX_VALUE, withCredentials: false }) .factory('FileUploader', ['fileUploaderOptions', '$rootScope', '$http', '$window', '$compile', function(fileUploaderOptions, $rootScope, $http, $window, $compile) { /** * Creates an instance of FileUploader * @param {Object} [options] * @constructor */ function FileUploader(options) { var settings = angular.copy(fileUploaderOptions); angular.extend(this, settings, options, { isUploading: false, _nextIndex: 0, _failFilterIndex: -1, _directives: {select: [], drop: [], over: []} }); // add default filters this.filters.unshift({name: 'queueLimit', fn: this._queueLimitFilter}); this.filters.unshift({name: 'folder', fn: this._folderFilter}); } /********************** * PUBLIC **********************/ /** * Checks a support the html5 uploader * @returns {Boolean} * @readonly */ FileUploader.prototype.isHTML5 = !!($window.File && $window.FormData); /** * Adds items to the queue * @param {File|HTMLInputElement|Object|FileList|Array} files * @param {Object} [options] * @param {Array|String} filters */ FileUploader.prototype.addToQueue = function(files, options, filters) { var list = this.isArrayLikeObject(files) ? files: [files]; var arrayOfFilters = this._getFilters(filters); var count = this.queue.length; var addedFileItems = []; angular.forEach(list, function(some /*{File|HTMLInputElement|Object}*/) { var temp = new FileUploader.FileLikeObject(some); if (this._isValidFile(temp, arrayOfFilters, options)) { var fileItem = new FileUploader.FileItem(this, some, options); addedFileItems.push(fileItem); this.queue.push(fileItem); this._onAfterAddingFile(fileItem); } else { var filter = arrayOfFilters[this._failFilterIndex]; this._onWhenAddingFileFailed(temp, filter, options); } }, this); if(this.queue.length !== count) { this._onAfterAddingAll(addedFileItems); this.progress = this._getTotalProgress(); } this._render(); if (this.autoUpload) this.uploadAll(); }; /** * Remove items from the queue. Remove last: index = -1 * @param {FileItem|Number} value */ FileUploader.prototype.removeFromQueue = function(value) { var index = this.getIndexOfItem(value); var item = this.queue[index]; if (item.isUploading) item.cancel(); this.queue.splice(index, 1); item._destroy(); this.progress = this._getTotalProgress(); }; /** * Clears the queue */ FileUploader.prototype.clearQueue = function() { while(this.queue.length) { this.queue[0].remove(); } this.progress = 0; }; /** * Uploads a item from the queue * @param {FileItem|Number} value */ FileUploader.prototype.uploadItem = function(value) { var index = this.getIndexOfItem(value); var item = this.queue[index]; var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport'; item._prepareToUploading(); if(this.isUploading) return; this.isUploading = true; this[transport](item); }; /** * Cancels uploading of item from the queue * @param {FileItem|Number} value */ FileUploader.prototype.cancelItem = function(value) { var index = this.getIndexOfItem(value); var item = this.queue[index]; var prop = this.isHTML5 ? '_xhr' : '_form'; if (item && item.isUploading) item[prop].abort(); }; /** * Uploads all not uploaded items of queue */ FileUploader.prototype.uploadAll = function() { var items = this.getNotUploadedItems().filter(function(item) { return !item.isUploading; }); if (!items.length) return; angular.forEach(items, function(item) { item._prepareToUploading(); }); items[0].upload(); }; /** * Cancels all uploads */ FileUploader.prototype.cancelAll = function() { var items = this.getNotUploadedItems(); angular.forEach(items, function(item) { item.cancel(); }); }; /** * Returns "true" if value an instance of File * @param {*} value * @returns {Boolean} * @private */ FileUploader.prototype.isFile = function(value) { var fn = $window.File; return (fn && value instanceof fn); }; /** * Returns "true" if value an instance of FileLikeObject * @param {*} value * @returns {Boolean} * @private */ FileUploader.prototype.isFileLikeObject = function(value) { return value instanceof FileUploader.FileLikeObject; }; /** * Returns "true" if value is array like object * @param {*} value * @returns {Boolean} */ FileUploader.prototype.isArrayLikeObject = function(value) { return (angular.isObject(value) && 'length' in value); }; /** * Returns a index of item from the queue * @param {Item|Number} value * @returns {Number} */ FileUploader.prototype.getIndexOfItem = function(value) { return angular.isNumber(value) ? value : this.queue.indexOf(value); }; /** * Returns not uploaded items * @returns {Array} */ FileUploader.prototype.getNotUploadedItems = function() { return this.queue.filter(function(item) { return !item.isUploaded; }); }; /** * Returns items ready for upload * @returns {Array} */ FileUploader.prototype.getReadyItems = function() { return this.queue .filter(function(item) { return (item.isReady && !item.isUploading); }) .sort(function(item1, item2) { return item1.index - item2.index; }); }; /** * Destroys instance of FileUploader */ FileUploader.prototype.destroy = function() { angular.forEach(this._directives, function(key) { angular.forEach(this._directives[key], function(object) { object.destroy(); }, this); }, this); }; /** * Callback * @param {Array} fileItems */ FileUploader.prototype.onAfterAddingAll = function(fileItems) {}; /** * Callback * @param {FileItem} fileItem */ FileUploader.prototype.onAfterAddingFile = function(fileItem) {}; /** * Callback * @param {File|Object} item * @param {Object} filter * @param {Object} options * @private */ FileUploader.prototype.onWhenAddingFileFailed = function(item, filter, options) {}; /** * Callback * @param {FileItem} fileItem */ FileUploader.prototype.onBeforeUploadItem = function(fileItem) {}; /** * Callback * @param {FileItem} fileItem * @param {Number} progress */ FileUploader.prototype.onProgressItem = function(fileItem, progress) {}; /** * Callback * @param {Number} progress */ FileUploader.prototype.onProgressAll = function(progress) {}; /** * Callback * @param {FileItem} item * @param {*} response * @param {Number} status * @param {Object} headers */ FileUploader.prototype.onSuccessItem = function(item, response, status, headers) {}; /** * Callback * @param {FileItem} item * @param {*} response * @param {Number} status * @param {Object} headers */ FileUploader.prototype.onErrorItem = function(item, response, status, headers) {}; /** * Callback * @param {FileItem} item * @param {*} response * @param {Number} status * @param {Object} headers */ FileUploader.prototype.onCancelItem = function(item, response, status, headers) {}; /** * Callback * @param {FileItem} item * @param {*} response * @param {Number} status * @param {Object} headers */ FileUploader.prototype.onCompleteItem = function(item, response, status, headers) {}; /** * Callback */ FileUploader.prototype.onCompleteAll = function() {}; /********************** * PRIVATE **********************/ /** * Returns the total progress * @param {Number} [value] * @returns {Number} * @private */ FileUploader.prototype._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); }; /** * Returns array of filters * @param {Array|String} filters * @returns {Array} * @private */ FileUploader.prototype._getFilters = function(filters) { if (angular.isUndefined(filters)) return this.filters; if (angular.isArray(filters)) return filters; var names = filters.match(/[^\s,]+/g); return this.filters.filter(function(filter) { return names.indexOf(filter.name) !== -1; }, this); }; /** * Updates html * @private */ FileUploader.prototype._render = function() { if (!$rootScope.$$phase) $rootScope.$apply(); }; /** * Returns "true" if item is a file (not folder) * @param {File|FileLikeObject} item * @returns {Boolean} * @private */ FileUploader.prototype._folderFilter = function(item) { return !!(item.size || item.type); }; /** * Returns "true" if the limit has not been reached * @returns {Boolean} * @private */ FileUploader.prototype._queueLimitFilter = function() { return this.queue.length < this.queueLimit; }; /** * Returns "true" if file pass all filters * @param {File|Object} file * @param {Array} filters * @param {Object} options * @returns {Boolean} * @private */ FileUploader.prototype._isValidFile = function(file, filters, options) { this._failFilterIndex = -1; return !filters.length ? true : filters.every(function(filter) { this._failFilterIndex++; return filter.fn.call(this, file, options); }, this); }; /** * Checks whether upload successful * @param {Number} status * @returns {Boolean} * @private */ FileUploader.prototype._isSuccessCode = function(status) { return (status >= 200 && status < 300) || status === 304; }; /** * Transforms the server response * @param {*} response * @param {Object} headers * @returns {*} * @private */ FileUploader.prototype._transformResponse = function(response, headers) { var headersGetter = this._headersGetter(headers); angular.forEach($http.defaults.transformResponse, function(transformFn) { response = transformFn(response, headersGetter); }); return response; }; /** * Parsed response headers * @param headers * @returns {Object} * @see https://github.com/angular/angular.js/blob/master/src/ng/http.js * @private */ FileUploader.prototype._parseHeaders = function(headers) { var parsed = {}, key, val, i; if (!headers) return parsed; angular.forEach(headers.split('\n'), function(line) { i = line.indexOf(':'); key = line.slice(0, i).trim().toLowerCase(); val = line.slice(i + 1).trim(); if (key) { parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; } }); return parsed; }; /** * Returns function that returns headers * @param {Object} parsedHeaders * @returns {Function} * @private */ FileUploader.prototype._headersGetter = function(parsedHeaders) { return function(name) { if (name) { return parsedHeaders[name.toLowerCase()] || null; } return parsedHeaders; }; }; /** * The XMLHttpRequest transport * @param {FileItem} item * @private */ FileUploader.prototype._xhrTransport = function(item) { var xhr = item._xhr = new XMLHttpRequest(); var form = new FormData(); var that = this; that._onBeforeUploadItem(item); angular.forEach(item.formData, function(obj) { angular.forEach(obj, function(value, key) { form.append(key, value); }); }); if ( typeof(item._file.size) != 'number' ) { throw new TypeError('The file specified is no longer valid'); } form.append(item.alias, item._file, item.file.name); xhr.upload.onprogress = function(event) { var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0); that._onProgressItem(item, progress); }; xhr.onload = function() { var headers = that._parseHeaders(xhr.getAllResponseHeaders()); var response = that._transformResponse(xhr.response, headers); var gist = that._isSuccessCode(xhr.status) ? 'Success' : 'Error'; var method = '_on' + gist + 'Item'; that[method](item, response, xhr.status, headers); that._onCompleteItem(item, response, xhr.status, headers); }; xhr.onerror = function() { var headers = that._parseHeaders(xhr.getAllResponseHeaders()); var response = that._transformResponse(xhr.response, headers); that._onErrorItem(item, response, xhr.status, headers); that._onCompleteItem(item, response, xhr.status, headers); }; xhr.onabort = function() { var headers = that._parseHeaders(xhr.getAllResponseHeaders()); var response = that._transformResponse(xhr.response, headers); that._onCancelItem(item, response, xhr.status, headers); that._onCompleteItem(item, response, xhr.status, headers); }; 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); this._render(); }; /** * The IFrame transport * @param {FileItem} item * @private */ FileUploader.prototype._iframeTransport = function(item) { var form = angular.element('
'); var iframe = angular.element('