├── .babelrc ├── .gitignore ├── .jshintrc ├── CONTRIBUTING.md ├── README.md ├── WebpackConfig.js ├── bower.json ├── dist ├── angular-file-upload.js ├── angular-file-upload.js.map ├── angular-file-upload.min.js └── angular-file-upload.min.js.map ├── examples ├── console-sham.js ├── console-sham.min.js ├── image-preview │ ├── controllers.js │ ├── directives.js │ ├── index.html │ ├── upload.php │ └── uploads │ │ └── gap.txt ├── issues │ ├── 862 │ │ ├── controllers.js │ │ └── index.html │ ├── 873 │ │ ├── controllers.js │ │ └── index.html │ ├── 874 │ │ └── index.html │ ├── pr876 │ │ ├── iframe.html │ │ ├── index.html │ │ └── uploadPage.html │ └── upload.php ├── simple │ ├── controllers.js │ ├── index.html │ ├── upload.php │ └── uploads │ │ └── gap.txt └── without-bootstrap │ ├── controllers.js │ ├── directives.js │ ├── index.html │ ├── style.css │ ├── upload.php │ └── uploads │ └── gap.txt ├── gulpfile.js ├── license.txt ├── package.json └── src ├── config.json ├── directives ├── FileDrop.js ├── FileOver.js └── FileSelect.js ├── index.js ├── services ├── FileDirective.js ├── FileDrop.js ├── FileItem.js ├── FileLikeObject.js ├── FileOver.js ├── FileSelect.js ├── FileUploader.js └── Pipeline.js └── values └── options.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "plugins": [ 6 | [ 7 | "transform-es2015-classes", 8 | { 9 | "loose": true 10 | } 11 | ] 12 | ] 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .temp 2 | .idea 3 | *.iml 4 | node_modules 5 | bower_components 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase" : true, 3 | "indent": 4, 4 | "strict": true, 5 | "undef": true, 6 | "unused": true, 7 | "quotmark": "single", 8 | "maxlen": 120, 9 | "trailing": true, 10 | "curly": true, 11 | 12 | "devel": true, 13 | 14 | "browser":true, 15 | "jquery":true, 16 | "predef": [ 17 | "angular", 18 | "define", 19 | "require", 20 | "app" 21 | ] 22 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Angular-File-Upload 2 | 3 | :+1::tada: Welcome in angular-file-upload community :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to Angular-File-Upload. 6 | These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 7 | 8 | Note : this guide is inspired by https://github.com/atom/atom/blob/master/CONTRIBUTING.md 9 | 10 | #### Table Of Contents 11 | 12 | [How Can I Contribute?](#how-can-i-contribute) 13 | * [Reporting Bugs](#reporting-bugs) 14 | * [Suggesting Enhancements](#suggesting-enhancements) 15 | * [Pull Requests](#pull-requests) 16 | 17 | [Additional Notes](#additional-notes) 18 | * [Issue and Pull Request Labels](#issue-and-pull-request-labels) 19 | * [Contributors Communication](#contributors-communication) 20 | 21 | ## How Can I Contribute? 22 | 23 | ### Reporting Bugs 24 | 25 | This section guides you through submitting a bug report for Angular-File-Upload. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. 26 | 27 | Fill out [the required template](ISSUE_TEMPLATE.md), the information it asks for helps us resolve issues faster. 28 | * Before Submitting A Bug Report : **Perform a [quick search](https://github.com/nervgh/angular-file-upload/issues)** to see if the problem has already been reported. If it has, add a comment to the existing issue instead of opening a new one. 29 | * When you are creating a bug report, please include as many details as possible, explain the problem and include additional details to help maintainers reproduce the problem: 30 | 31 | * **Use a clear and descriptive title** for the issue to identify the problem. 32 | * **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you are using Angulare-File-Upload. When listing steps, **don't just say what you did, but explain how you did it**. For example, if you upload a file, explain if you did it by clicking on a button or if you have started the upload programmatically? 33 | * **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 34 | * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 35 | * **Explain which behavior you expected to see instead and why.** 36 | * **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. 37 | * **If you're reporting that Angular-File-Upload crashed**, include a crash report with a stack trace from the browser stack. On macOS, the crash report will be available and put it in a [gist](https://gist.github.com/) and provide link to that gist. 38 | 39 | Provide more context by answering these questions: 40 | 41 | * **Did the problem start happening recently** (e.g. after updating to a new version of Angular-File-Upload) or was this always a problem? 42 | * **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. 43 | * **Does the problem happen for all files or only some?** Does the problem happen only when working with local or remote files (e.g. on network drives), with files of a specific type (e.g. only JavaScript or Python files), with large files or files with very long lines, or with files in a specific encoding? Is there anything else special about the files you are using? 44 | 45 | Include details about your configuration and environment: 46 | 47 | * **Which browser are you using?** ? 48 | * **What's the name and version of the OS you're using**? 49 | 50 | ### Suggesting Enhancements 51 | 52 | This section guides you through submitting an enhancement suggestion for Angular-File-Upload, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. 53 | * Before Submitting An Enhancement : **Perform a [quick search](https://github.com/nervgh/angular-file-upload/issues)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. 54 | 55 | Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). Provide the following information: 56 | 57 | * **Use a clear and descriptive title** for the issue to identify the suggestion. 58 | * **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 59 | * **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 60 | * **Describe the current behavior** and **explain which behavior you expected to see instead** and why. 61 | * **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Angular-File-Upload which the suggestion is related to. You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. 62 | * **Explain why this enhancement would be useful** to most Angular-File-Upload users. 63 | * **List some other uploader libs or usages where this enhancement exists.** 64 | 65 | ### Pull Requests 66 | 67 | * Follow [standardJS](https://github.com/feross/standard) styleguides. 68 | * Include thoughtfully-worded, [protractor](http://www.protractortest.org/#/) tests 69 | * Document new code based using [jsdoc](http://usejsdoc.org/) 70 | 71 | ## Additional Notes 72 | 73 | ### Issue and Pull Request Labels 74 | 75 | Feel free to grade issues and PRs by tags if it helps you work. 76 | You can create any labels what you need and you can manage project using these labels. 77 | 78 | ### Contributors Communication 79 | 80 | To participate to discussion, please ask access to [nervgh](https://github.com/nervgh) to the telegram group of contributors. 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular File Upload 2 | 3 | + compatible with AngularJS v1.x 4 | 5 | --- 6 | 7 | ## About 8 | 9 | **Angular File Upload** is a module for the [AngularJS](http://angularjs.org/) framework. Supports drag-n-drop upload, upload progress, validation filters and a file upload queue. It supports native HTML5 uploads, but degrades to a legacy iframe upload method for older browsers. Works with any server side platform which supports standard HTML form uploads. 10 | 11 | When files are selected or dropped into the component, one or more filters are applied. Files which pass all filters are added to the queue. When file is added to the queue, for him is created instance of `{FileItem}` and uploader options are copied into this object. After, items in the queue (FileItems) are ready for uploading. 12 | 13 | ## Package managers 14 | 15 | ### NPM [![npm](https://img.shields.io/npm/v/angular-file-upload.svg)](https://www.npmjs.com/package/angular-file-upload) 16 | ``` 17 | npm install angular-file-upload 18 | ``` 19 | You could find this module in npm like [_angular file upload_](https://www.npmjs.com/package/angular-file-upload). 20 | 21 | ### Yarn [![npm](https://img.shields.io/npm/v/angular-file-upload.svg)](https://www.npmjs.com/package/angular-file-upload) 22 | ``` 23 | yarn add --exact angular-file-upload 24 | ``` 25 | You could find this module in yarn like [_angular file upload_](https://yarnpkg.com/en/package/angular-file-upload). 26 | 27 | ### Module Dependency 28 | 29 | Add `'angularFileUpload'` to your module declaration: 30 | 31 | ``` 32 | var app = angular.module('my-app', [ 33 | 'angularFileUpload' 34 | ]); 35 | ``` 36 | 37 | ## Demos 38 | 1. [Simple example](http://nervgh.github.io/pages/angular-file-upload/examples/simple) 39 | 2. [Uploads only images (with canvas preview)](http://nervgh.github.io/pages/angular-file-upload/examples/image-preview) 40 | 3. [Without bootstrap example](http://nervgh.github.io/pages/angular-file-upload/examples/without-bootstrap) 41 | 42 | ## More Info 43 | 44 | 1. [Introduction](https://github.com/nervgh/angular-file-upload/wiki/Introduction) 45 | 2. [Module API](https://github.com/nervgh/angular-file-upload/wiki/Module-API) 46 | 3. [FAQ](https://github.com/nervgh/angular-file-upload/wiki/FAQ) 47 | 4. [Migrate from 0.x.x to 1.x.x](https://github.com/nervgh/angular-file-upload/wiki/Migrate-from-0.x.x-to-1.x.x) 48 | 5. [RubyGem](https://github.com/marthyn/angularjs-file-upload-rails) 49 | 50 | ## Browser compatibility 51 | This module uses the _feature detection_ pattern for adaptation its behaviour: [fd1](https://github.com/nervgh/angular-file-upload/blob/v2.3.1/src/services/FileUploader.js#L728), 52 | [fd2](https://github.com/nervgh/angular-file-upload/blob/v2.3.1/examples/image-preview/directives.js#L21). 53 | 54 | You could check out features of target browsers using http://caniuse.com/. For example, the [File API](http://caniuse.com/#feat=fileapi) feature. 55 | 56 | | Feature/Browser | IE 8-9 | IE10+ | Firefox 28+ | Chrome 38+ | Safari 6+ | 57 | |----------|:---:|:---:|:---:|:---:|:---:| 58 | | `` | + | + | + | + | + | 59 | | `` | - | + | + | + | + | 60 | | Drag-n-drop | - | + | + | + | + | 61 | | Iframe transport (only for old browsers) | + | + | + | + | + | 62 | | XHR transport (multipart,binary) | - | + | + | + | + | 63 | | An image preview via Canvas (not built-in) | - | + | + | + | + | 64 | | AJAX headers | - | + | + | + | + | 65 | 66 | 67 | ## How to ask a question 68 | 69 | ### A right way to ask a question 70 | If you have a question, please, follow next steps: 71 | - Try to find an answer to your question using [search](https://github.com/nervgh/angular-file-upload/issues?utf8=%E2%9C%93&q=) 72 | - If you have not found an answer, create [new issue](https://github.com/nervgh/angular-file-upload/issues/new) on issue-tracker 73 | 74 | ### Why email a question is a bad way? 75 | When you emal me a question: 76 | - You lose an opportunity to get an answer from other team members or users (devs) 77 | - It requires from me to answer on same questions again and again 78 | - It is not a rational way. For example, if everybody who use code of this project will have emailed me a question then I will be receiving ~700 emails each day =) 79 | - It is a very slow way. I have not time for it. 80 | -------------------------------------------------------------------------------- /WebpackConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // http://webpack.github.io/docs/ 4 | let webpack = require('webpack'); 5 | 6 | 7 | class WebpackConfig { 8 | /** 9 | * @param {Object} descriptor 10 | * @param {String} descriptor.name 11 | * @param {String} descriptor.version 12 | * @param {String} descriptor.homepage 13 | * @param {Object} path 14 | * @param {String} path.src 15 | * @param {String} path.dist 16 | */ 17 | constructor(descriptor, path) { 18 | this.name = descriptor.name; 19 | this.version = descriptor.version; 20 | this.homepage = descriptor.homepage; 21 | this.path = path; 22 | } 23 | /** 24 | * @returns {Object} 25 | */ 26 | get() { 27 | let entry = {}; 28 | entry[this.name] = this.path.src; 29 | entry[this.name + '.min'] = this.path.src; 30 | 31 | return { 32 | entry, 33 | module: { 34 | loaders: [ 35 | // https://github.com/babel/babel-loader 36 | { 37 | test: /\.js$/, 38 | loader: 'babel' 39 | }, 40 | // https://github.com/webpack/json-loader 41 | {test: /\.json$/, loader: 'json'}, 42 | // https://github.com/webpack/html-loader 43 | {test: /\.html$/, loader: 'raw'} 44 | ] 45 | }, 46 | devtool: 'source-map', 47 | output: { 48 | libraryTarget: 'umd', 49 | library: this.name, 50 | filename: '[name].js' 51 | }, 52 | plugins: [ 53 | new webpack.optimize.UglifyJsPlugin({ 54 | // Minify only [name].min.js file 55 | // http://stackoverflow.com/a/34018909 56 | include: /\.min\.js$/ 57 | }), 58 | new webpack.BannerPlugin( 59 | `/*\n` + 60 | ` ${this.name} v${this.version}\n` + 61 | ` ${this.homepage}\n` + 62 | `*/\n` 63 | , { 64 | entryOnly: true, 65 | raw: true 66 | }) 67 | ] 68 | }; 69 | } 70 | } 71 | 72 | module.exports = WebpackConfig; -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-file-upload", 3 | "main": "dist/angular-file-upload.min.js", 4 | "homepage": "https://github.com/nervgh/angular-file-upload", 5 | "ignore": ["examples"], 6 | "dependencies": { 7 | "angular": "^1.1.5" 8 | }, 9 | "devDependencies": { 10 | "es5-shim": ">=3.4.0" 11 | }, 12 | "keywords": [ 13 | "angular", 14 | "file", 15 | "upload", 16 | "module" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /dist/angular-file-upload.js: -------------------------------------------------------------------------------- 1 | /* 2 | angular-file-upload v2.6.1 3 | https://github.com/nervgh/angular-file-upload 4 | */ 5 | 6 | (function webpackUniversalModuleDefinition(root, factory) { 7 | if(typeof exports === 'object' && typeof module === 'object') 8 | module.exports = factory(); 9 | else if(typeof define === 'function' && define.amd) 10 | define([], factory); 11 | else if(typeof exports === 'object') 12 | exports["angular-file-upload"] = factory(); 13 | else 14 | root["angular-file-upload"] = factory(); 15 | })(this, function() { 16 | return /******/ (function(modules) { // webpackBootstrap 17 | /******/ // The module cache 18 | /******/ var installedModules = {}; 19 | /******/ 20 | /******/ // The require function 21 | /******/ function __webpack_require__(moduleId) { 22 | /******/ 23 | /******/ // Check if module is in cache 24 | /******/ if(installedModules[moduleId]) 25 | /******/ return installedModules[moduleId].exports; 26 | /******/ 27 | /******/ // Create a new module (and put it into the cache) 28 | /******/ var module = installedModules[moduleId] = { 29 | /******/ exports: {}, 30 | /******/ id: moduleId, 31 | /******/ loaded: false 32 | /******/ }; 33 | /******/ 34 | /******/ // Execute the module function 35 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 36 | /******/ 37 | /******/ // Flag the module as loaded 38 | /******/ module.loaded = true; 39 | /******/ 40 | /******/ // Return the exports of the module 41 | /******/ return module.exports; 42 | /******/ } 43 | /******/ 44 | /******/ 45 | /******/ // expose the modules object (__webpack_modules__) 46 | /******/ __webpack_require__.m = modules; 47 | /******/ 48 | /******/ // expose the module cache 49 | /******/ __webpack_require__.c = installedModules; 50 | /******/ 51 | /******/ // __webpack_public_path__ 52 | /******/ __webpack_require__.p = ""; 53 | /******/ 54 | /******/ // Load entry module and return exports 55 | /******/ return __webpack_require__(0); 56 | /******/ }) 57 | /************************************************************************/ 58 | /******/ ([ 59 | /* 0 */ 60 | /***/ (function(module, exports, __webpack_require__) { 61 | 62 | 'use strict'; 63 | 64 | var _config = __webpack_require__(1); 65 | 66 | var _config2 = _interopRequireDefault(_config); 67 | 68 | var _options = __webpack_require__(2); 69 | 70 | var _options2 = _interopRequireDefault(_options); 71 | 72 | var _FileUploader = __webpack_require__(3); 73 | 74 | var _FileUploader2 = _interopRequireDefault(_FileUploader); 75 | 76 | var _FileLikeObject = __webpack_require__(4); 77 | 78 | var _FileLikeObject2 = _interopRequireDefault(_FileLikeObject); 79 | 80 | var _FileItem = __webpack_require__(5); 81 | 82 | var _FileItem2 = _interopRequireDefault(_FileItem); 83 | 84 | var _FileDirective = __webpack_require__(6); 85 | 86 | var _FileDirective2 = _interopRequireDefault(_FileDirective); 87 | 88 | var _FileSelect = __webpack_require__(7); 89 | 90 | var _FileSelect2 = _interopRequireDefault(_FileSelect); 91 | 92 | var _Pipeline = __webpack_require__(8); 93 | 94 | var _Pipeline2 = _interopRequireDefault(_Pipeline); 95 | 96 | var _FileDrop = __webpack_require__(9); 97 | 98 | var _FileDrop2 = _interopRequireDefault(_FileDrop); 99 | 100 | var _FileOver = __webpack_require__(10); 101 | 102 | var _FileOver2 = _interopRequireDefault(_FileOver); 103 | 104 | var _FileSelect3 = __webpack_require__(11); 105 | 106 | var _FileSelect4 = _interopRequireDefault(_FileSelect3); 107 | 108 | var _FileDrop3 = __webpack_require__(12); 109 | 110 | var _FileDrop4 = _interopRequireDefault(_FileDrop3); 111 | 112 | var _FileOver3 = __webpack_require__(13); 113 | 114 | var _FileOver4 = _interopRequireDefault(_FileOver3); 115 | 116 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 117 | 118 | angular.module(_config2.default.name, []).value('fileUploaderOptions', _options2.default).factory('FileUploader', _FileUploader2.default).factory('FileLikeObject', _FileLikeObject2.default).factory('FileItem', _FileItem2.default).factory('FileDirective', _FileDirective2.default).factory('FileSelect', _FileSelect2.default).factory('FileDrop', _FileDrop2.default).factory('FileOver', _FileOver2.default).factory('Pipeline', _Pipeline2.default).directive('nvFileSelect', _FileSelect4.default).directive('nvFileDrop', _FileDrop4.default).directive('nvFileOver', _FileOver4.default).run(['FileUploader', 'FileLikeObject', 'FileItem', 'FileDirective', 'FileSelect', 'FileDrop', 'FileOver', 'Pipeline', function (FileUploader, FileLikeObject, FileItem, FileDirective, FileSelect, FileDrop, FileOver, Pipeline) { 119 | // only for compatibility 120 | FileUploader.FileLikeObject = FileLikeObject; 121 | FileUploader.FileItem = FileItem; 122 | FileUploader.FileDirective = FileDirective; 123 | FileUploader.FileSelect = FileSelect; 124 | FileUploader.FileDrop = FileDrop; 125 | FileUploader.FileOver = FileOver; 126 | FileUploader.Pipeline = Pipeline; 127 | }]); 128 | 129 | /***/ }), 130 | /* 1 */ 131 | /***/ (function(module, exports) { 132 | 133 | module.exports = {"name":"angularFileUpload"} 134 | 135 | /***/ }), 136 | /* 2 */ 137 | /***/ (function(module, exports) { 138 | 139 | 'use strict'; 140 | 141 | Object.defineProperty(exports, "__esModule", { 142 | value: true 143 | }); 144 | exports.default = { 145 | url: '/', 146 | alias: 'file', 147 | headers: {}, 148 | queue: [], 149 | progress: 0, 150 | autoUpload: false, 151 | removeAfterUpload: false, 152 | method: 'POST', 153 | filters: [], 154 | formData: [], 155 | queueLimit: Number.MAX_VALUE, 156 | withCredentials: false, 157 | disableMultipart: false 158 | }; 159 | 160 | /***/ }), 161 | /* 3 */ 162 | /***/ (function(module, exports, __webpack_require__) { 163 | 164 | 'use strict'; 165 | 166 | Object.defineProperty(exports, "__esModule", { 167 | value: true 168 | }); 169 | 170 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 171 | 172 | exports.default = __identity; 173 | 174 | var _config = __webpack_require__(1); 175 | 176 | var _config2 = _interopRequireDefault(_config); 177 | 178 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 179 | 180 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 181 | 182 | var _angular = angular, 183 | bind = _angular.bind, 184 | copy = _angular.copy, 185 | extend = _angular.extend, 186 | forEach = _angular.forEach, 187 | isObject = _angular.isObject, 188 | isNumber = _angular.isNumber, 189 | isDefined = _angular.isDefined, 190 | isArray = _angular.isArray, 191 | isUndefined = _angular.isUndefined, 192 | element = _angular.element; 193 | function __identity(fileUploaderOptions, $rootScope, $http, $window, $timeout, FileLikeObject, FileItem, Pipeline) { 194 | var File = $window.File, 195 | FormData = $window.FormData; 196 | 197 | var FileUploader = function () { 198 | /********************** 199 | * PUBLIC 200 | **********************/ 201 | /** 202 | * Creates an instance of FileUploader 203 | * @param {Object} [options] 204 | * @constructor 205 | */ 206 | function FileUploader(options) { 207 | _classCallCheck(this, FileUploader); 208 | 209 | var settings = copy(fileUploaderOptions); 210 | 211 | extend(this, settings, options, { 212 | isUploading: false, 213 | _nextIndex: 0, 214 | _directives: { select: [], drop: [], over: [] } 215 | }); 216 | 217 | // add default filters 218 | this.filters.unshift({ name: 'queueLimit', fn: this._queueLimitFilter }); 219 | this.filters.unshift({ name: 'folder', fn: this._folderFilter }); 220 | } 221 | /** 222 | * Adds items to the queue 223 | * @param {File|HTMLInputElement|Object|FileList|Array} files 224 | * @param {Object} [options] 225 | * @param {Array|String} filters 226 | */ 227 | 228 | 229 | FileUploader.prototype.addToQueue = function addToQueue(files, options, filters) { 230 | var _this = this; 231 | 232 | var incomingQueue = this.isArrayLikeObject(files) ? Array.prototype.slice.call(files) : [files]; 233 | var arrayOfFilters = this._getFilters(filters); 234 | var count = this.queue.length; 235 | var addedFileItems = []; 236 | 237 | var next = function next() { 238 | var something = incomingQueue.shift(); 239 | 240 | if (isUndefined(something)) { 241 | return done(); 242 | } 243 | 244 | var fileLikeObject = _this.isFile(something) ? something : new FileLikeObject(something); 245 | var pipes = _this._convertFiltersToPipes(arrayOfFilters); 246 | var pipeline = new Pipeline(pipes); 247 | var onThrown = function onThrown(err) { 248 | var originalFilter = err.pipe.originalFilter; 249 | 250 | var _err$args = _slicedToArray(err.args, 2), 251 | fileLikeObject = _err$args[0], 252 | options = _err$args[1]; 253 | 254 | _this._onWhenAddingFileFailed(fileLikeObject, originalFilter, options); 255 | next(); 256 | }; 257 | var onSuccessful = function onSuccessful(fileLikeObject, options) { 258 | var fileItem = new FileItem(_this, fileLikeObject, options); 259 | addedFileItems.push(fileItem); 260 | _this.queue.push(fileItem); 261 | _this._onAfterAddingFile(fileItem); 262 | next(); 263 | }; 264 | pipeline.onThrown = onThrown; 265 | pipeline.onSuccessful = onSuccessful; 266 | pipeline.exec(fileLikeObject, options); 267 | }; 268 | 269 | var done = function done() { 270 | if (_this.queue.length !== count) { 271 | _this._onAfterAddingAll(addedFileItems); 272 | _this.progress = _this._getTotalProgress(); 273 | } 274 | 275 | _this._render(); 276 | if (_this.autoUpload) _this.uploadAll(); 277 | }; 278 | 279 | next(); 280 | }; 281 | /** 282 | * Remove items from the queue. Remove last: index = -1 283 | * @param {FileItem|Number} value 284 | */ 285 | 286 | 287 | FileUploader.prototype.removeFromQueue = function removeFromQueue(value) { 288 | var index = this.getIndexOfItem(value); 289 | var item = this.queue[index]; 290 | if (item.isUploading) item.cancel(); 291 | this.queue.splice(index, 1); 292 | item._destroy(); 293 | this.progress = this._getTotalProgress(); 294 | }; 295 | /** 296 | * Clears the queue 297 | */ 298 | 299 | 300 | FileUploader.prototype.clearQueue = function clearQueue() { 301 | while (this.queue.length) { 302 | this.queue[0].remove(); 303 | } 304 | this.progress = 0; 305 | }; 306 | /** 307 | * Uploads a item from the queue 308 | * @param {FileItem|Number} value 309 | */ 310 | 311 | 312 | FileUploader.prototype.uploadItem = function uploadItem(value) { 313 | var index = this.getIndexOfItem(value); 314 | var item = this.queue[index]; 315 | var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport'; 316 | 317 | item._prepareToUploading(); 318 | if (this.isUploading) return; 319 | 320 | this._onBeforeUploadItem(item); 321 | if (item.isCancel) return; 322 | 323 | item.isUploading = true; 324 | this.isUploading = true; 325 | this[transport](item); 326 | this._render(); 327 | }; 328 | /** 329 | * Cancels uploading of item from the queue 330 | * @param {FileItem|Number} value 331 | */ 332 | 333 | 334 | FileUploader.prototype.cancelItem = function cancelItem(value) { 335 | var _this2 = this; 336 | 337 | var index = this.getIndexOfItem(value); 338 | var item = this.queue[index]; 339 | var prop = this.isHTML5 ? '_xhr' : '_form'; 340 | if (!item) return; 341 | item.isCancel = true; 342 | if (item.isUploading) { 343 | // It will call this._onCancelItem() & this._onCompleteItem() asynchronously 344 | item[prop].abort(); 345 | } else { 346 | var dummy = [undefined, 0, {}]; 347 | var onNextTick = function onNextTick() { 348 | _this2._onCancelItem.apply(_this2, [item].concat(dummy)); 349 | _this2._onCompleteItem.apply(_this2, [item].concat(dummy)); 350 | }; 351 | $timeout(onNextTick); // Trigger callbacks asynchronously (setImmediate emulation) 352 | } 353 | }; 354 | /** 355 | * Uploads all not uploaded items of queue 356 | */ 357 | 358 | 359 | FileUploader.prototype.uploadAll = function uploadAll() { 360 | var items = this.getNotUploadedItems().filter(function (item) { 361 | return !item.isUploading; 362 | }); 363 | if (!items.length) return; 364 | 365 | forEach(items, function (item) { 366 | return item._prepareToUploading(); 367 | }); 368 | items[0].upload(); 369 | }; 370 | /** 371 | * Cancels all uploads 372 | */ 373 | 374 | 375 | FileUploader.prototype.cancelAll = function cancelAll() { 376 | var items = this.getNotUploadedItems(); 377 | forEach(items, function (item) { 378 | return item.cancel(); 379 | }); 380 | }; 381 | /** 382 | * Returns "true" if value an instance of File 383 | * @param {*} value 384 | * @returns {Boolean} 385 | * @private 386 | */ 387 | 388 | 389 | FileUploader.prototype.isFile = function isFile(value) { 390 | return this.constructor.isFile(value); 391 | }; 392 | /** 393 | * Returns "true" if value an instance of FileLikeObject 394 | * @param {*} value 395 | * @returns {Boolean} 396 | * @private 397 | */ 398 | 399 | 400 | FileUploader.prototype.isFileLikeObject = function isFileLikeObject(value) { 401 | return this.constructor.isFileLikeObject(value); 402 | }; 403 | /** 404 | * Returns "true" if value is array like object 405 | * @param {*} value 406 | * @returns {Boolean} 407 | */ 408 | 409 | 410 | FileUploader.prototype.isArrayLikeObject = function isArrayLikeObject(value) { 411 | return this.constructor.isArrayLikeObject(value); 412 | }; 413 | /** 414 | * Returns a index of item from the queue 415 | * @param {Item|Number} value 416 | * @returns {Number} 417 | */ 418 | 419 | 420 | FileUploader.prototype.getIndexOfItem = function getIndexOfItem(value) { 421 | return isNumber(value) ? value : this.queue.indexOf(value); 422 | }; 423 | /** 424 | * Returns not uploaded items 425 | * @returns {Array} 426 | */ 427 | 428 | 429 | FileUploader.prototype.getNotUploadedItems = function getNotUploadedItems() { 430 | return this.queue.filter(function (item) { 431 | return !item.isUploaded; 432 | }); 433 | }; 434 | /** 435 | * Returns items ready for upload 436 | * @returns {Array} 437 | */ 438 | 439 | 440 | FileUploader.prototype.getReadyItems = function getReadyItems() { 441 | return this.queue.filter(function (item) { 442 | return item.isReady && !item.isUploading; 443 | }).sort(function (item1, item2) { 444 | return item1.index - item2.index; 445 | }); 446 | }; 447 | /** 448 | * Destroys instance of FileUploader 449 | */ 450 | 451 | 452 | FileUploader.prototype.destroy = function destroy() { 453 | var _this3 = this; 454 | 455 | forEach(this._directives, function (key) { 456 | forEach(_this3._directives[key], function (object) { 457 | object.destroy(); 458 | }); 459 | }); 460 | }; 461 | /** 462 | * Callback 463 | * @param {Array} fileItems 464 | */ 465 | 466 | 467 | FileUploader.prototype.onAfterAddingAll = function onAfterAddingAll(fileItems) {}; 468 | /** 469 | * Callback 470 | * @param {FileItem} fileItem 471 | */ 472 | 473 | 474 | FileUploader.prototype.onAfterAddingFile = function onAfterAddingFile(fileItem) {}; 475 | /** 476 | * Callback 477 | * @param {File|Object} item 478 | * @param {Object} filter 479 | * @param {Object} options 480 | */ 481 | 482 | 483 | FileUploader.prototype.onWhenAddingFileFailed = function onWhenAddingFileFailed(item, filter, options) {}; 484 | /** 485 | * Callback 486 | * @param {FileItem} fileItem 487 | */ 488 | 489 | 490 | FileUploader.prototype.onBeforeUploadItem = function onBeforeUploadItem(fileItem) {}; 491 | /** 492 | * Callback 493 | * @param {FileItem} fileItem 494 | * @param {Number} progress 495 | */ 496 | 497 | 498 | FileUploader.prototype.onProgressItem = function onProgressItem(fileItem, progress) {}; 499 | /** 500 | * Callback 501 | * @param {Number} progress 502 | */ 503 | 504 | 505 | FileUploader.prototype.onProgressAll = function onProgressAll(progress) {}; 506 | /** 507 | * Callback 508 | * @param {FileItem} item 509 | * @param {*} response 510 | * @param {Number} status 511 | * @param {Object} headers 512 | */ 513 | 514 | 515 | FileUploader.prototype.onSuccessItem = function onSuccessItem(item, response, status, headers) {}; 516 | /** 517 | * Callback 518 | * @param {FileItem} item 519 | * @param {*} response 520 | * @param {Number} status 521 | * @param {Object} headers 522 | */ 523 | 524 | 525 | FileUploader.prototype.onErrorItem = function onErrorItem(item, response, status, headers) {}; 526 | /** 527 | * Callback 528 | * @param {FileItem} item 529 | * @param {*} response 530 | * @param {Number} status 531 | * @param {Object} headers 532 | */ 533 | 534 | 535 | FileUploader.prototype.onCancelItem = function onCancelItem(item, response, status, headers) {}; 536 | /** 537 | * Callback 538 | * @param {FileItem} item 539 | * @param {*} response 540 | * @param {Number} status 541 | * @param {Object} headers 542 | */ 543 | 544 | 545 | FileUploader.prototype.onCompleteItem = function onCompleteItem(item, response, status, headers) {}; 546 | /** 547 | * Callback 548 | * @param {FileItem} item 549 | */ 550 | 551 | 552 | FileUploader.prototype.onTimeoutItem = function onTimeoutItem(item) {}; 553 | /** 554 | * Callback 555 | */ 556 | 557 | 558 | FileUploader.prototype.onCompleteAll = function onCompleteAll() {}; 559 | /********************** 560 | * PRIVATE 561 | **********************/ 562 | /** 563 | * Returns the total progress 564 | * @param {Number} [value] 565 | * @returns {Number} 566 | * @private 567 | */ 568 | 569 | 570 | FileUploader.prototype._getTotalProgress = function _getTotalProgress(value) { 571 | if (this.removeAfterUpload) return value || 0; 572 | 573 | var notUploaded = this.getNotUploadedItems().length; 574 | var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length; 575 | var ratio = 100 / this.queue.length; 576 | var current = (value || 0) * ratio / 100; 577 | 578 | return Math.round(uploaded * ratio + current); 579 | }; 580 | /** 581 | * Returns array of filters 582 | * @param {Array|String} filters 583 | * @returns {Array} 584 | * @private 585 | */ 586 | 587 | 588 | FileUploader.prototype._getFilters = function _getFilters(filters) { 589 | if (!filters) return this.filters; 590 | if (isArray(filters)) return filters; 591 | var names = filters.match(/[^\s,]+/g); 592 | return this.filters.filter(function (filter) { 593 | return names.indexOf(filter.name) !== -1; 594 | }); 595 | }; 596 | /** 597 | * @param {Array} filters 598 | * @returns {Array} 599 | * @private 600 | */ 601 | 602 | 603 | FileUploader.prototype._convertFiltersToPipes = function _convertFiltersToPipes(filters) { 604 | var _this4 = this; 605 | 606 | return filters.map(function (filter) { 607 | var fn = bind(_this4, filter.fn); 608 | fn.isAsync = filter.fn.length === 3; 609 | fn.originalFilter = filter; 610 | return fn; 611 | }); 612 | }; 613 | /** 614 | * Updates html 615 | * @private 616 | */ 617 | 618 | 619 | FileUploader.prototype._render = function _render() { 620 | if (!$rootScope.$$phase) $rootScope.$apply(); 621 | }; 622 | /** 623 | * Returns "true" if item is a file (not folder) 624 | * @param {File|FileLikeObject} item 625 | * @returns {Boolean} 626 | * @private 627 | */ 628 | 629 | 630 | FileUploader.prototype._folderFilter = function _folderFilter(item) { 631 | return !!(item.size || item.type); 632 | }; 633 | /** 634 | * Returns "true" if the limit has not been reached 635 | * @returns {Boolean} 636 | * @private 637 | */ 638 | 639 | 640 | FileUploader.prototype._queueLimitFilter = function _queueLimitFilter() { 641 | return this.queue.length < this.queueLimit; 642 | }; 643 | /** 644 | * Checks whether upload successful 645 | * @param {Number} status 646 | * @returns {Boolean} 647 | * @private 648 | */ 649 | 650 | 651 | FileUploader.prototype._isSuccessCode = function _isSuccessCode(status) { 652 | return status >= 200 && status < 300 || status === 304; 653 | }; 654 | /** 655 | * Transforms the server response 656 | * @param {*} response 657 | * @param {Object} headers 658 | * @returns {*} 659 | * @private 660 | */ 661 | 662 | 663 | FileUploader.prototype._transformResponse = function _transformResponse(response, headers) { 664 | var headersGetter = this._headersGetter(headers); 665 | forEach($http.defaults.transformResponse, function (transformFn) { 666 | response = transformFn(response, headersGetter); 667 | }); 668 | return response; 669 | }; 670 | /** 671 | * Parsed response headers 672 | * @param headers 673 | * @returns {Object} 674 | * @see https://github.com/angular/angular.js/blob/master/src/ng/http.js 675 | * @private 676 | */ 677 | 678 | 679 | FileUploader.prototype._parseHeaders = function _parseHeaders(headers) { 680 | var parsed = {}, 681 | key, 682 | val, 683 | i; 684 | 685 | if (!headers) return parsed; 686 | 687 | forEach(headers.split('\n'), function (line) { 688 | i = line.indexOf(':'); 689 | key = line.slice(0, i).trim().toLowerCase(); 690 | val = line.slice(i + 1).trim(); 691 | 692 | if (key) { 693 | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 694 | } 695 | }); 696 | 697 | return parsed; 698 | }; 699 | /** 700 | * Returns function that returns headers 701 | * @param {Object} parsedHeaders 702 | * @returns {Function} 703 | * @private 704 | */ 705 | 706 | 707 | FileUploader.prototype._headersGetter = function _headersGetter(parsedHeaders) { 708 | return function (name) { 709 | if (name) { 710 | return parsedHeaders[name.toLowerCase()] || null; 711 | } 712 | return parsedHeaders; 713 | }; 714 | }; 715 | /** 716 | * The XMLHttpRequest transport 717 | * @param {FileItem} item 718 | * @private 719 | */ 720 | 721 | 722 | FileUploader.prototype._xhrTransport = function _xhrTransport(item) { 723 | var _this5 = this; 724 | 725 | var xhr = item._xhr = new XMLHttpRequest(); 726 | var sendable; 727 | 728 | if (!item.disableMultipart) { 729 | sendable = new FormData(); 730 | forEach(item.formData, function (obj) { 731 | forEach(obj, function (value, key) { 732 | sendable.append(key, value); 733 | }); 734 | }); 735 | 736 | sendable.append(item.alias, item._file, item.file.name); 737 | } else { 738 | sendable = item._file; 739 | } 740 | 741 | if (typeof item._file.size != 'number') { 742 | throw new TypeError('The file specified is no longer valid'); 743 | } 744 | 745 | xhr.upload.onprogress = function (event) { 746 | var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0); 747 | _this5._onProgressItem(item, progress); 748 | }; 749 | 750 | xhr.onload = function () { 751 | var headers = _this5._parseHeaders(xhr.getAllResponseHeaders()); 752 | var response = _this5._transformResponse(xhr.response, headers); 753 | var gist = _this5._isSuccessCode(xhr.status) ? 'Success' : 'Error'; 754 | var method = '_on' + gist + 'Item'; 755 | _this5[method](item, response, xhr.status, headers); 756 | _this5._onCompleteItem(item, response, xhr.status, headers); 757 | }; 758 | 759 | xhr.onerror = function () { 760 | var headers = _this5._parseHeaders(xhr.getAllResponseHeaders()); 761 | var response = _this5._transformResponse(xhr.response, headers); 762 | _this5._onErrorItem(item, response, xhr.status, headers); 763 | _this5._onCompleteItem(item, response, xhr.status, headers); 764 | }; 765 | 766 | xhr.onabort = function () { 767 | var headers = _this5._parseHeaders(xhr.getAllResponseHeaders()); 768 | var response = _this5._transformResponse(xhr.response, headers); 769 | _this5._onCancelItem(item, response, xhr.status, headers); 770 | _this5._onCompleteItem(item, response, xhr.status, headers); 771 | }; 772 | 773 | xhr.ontimeout = function (e) { 774 | var headers = _this5._parseHeaders(xhr.getAllResponseHeaders()); 775 | var response = "Request Timeout."; 776 | _this5._onTimeoutItem(item); 777 | _this5._onCompleteItem(item, response, 408, headers); 778 | }; 779 | 780 | xhr.open(item.method, item.url, true); 781 | 782 | xhr.timeout = item.timeout || 0; 783 | xhr.withCredentials = item.withCredentials; 784 | 785 | forEach(item.headers, function (value, name) { 786 | xhr.setRequestHeader(name, value); 787 | }); 788 | 789 | xhr.send(sendable); 790 | }; 791 | /** 792 | * The IFrame transport 793 | * @param {FileItem} item 794 | * @private 795 | */ 796 | 797 | 798 | FileUploader.prototype._iframeTransport = function _iframeTransport(item) { 799 | var _this6 = this; 800 | 801 | var form = element('
'); 802 | var iframe = element(' 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/issues/upload.php: -------------------------------------------------------------------------------- 1 | 'File transfer completed' ); 11 | $json = json_encode( $answer ); 12 | 13 | echo $json; 14 | 15 | } else { 16 | 17 | echo 'No files'; 18 | 19 | } 20 | 21 | ?> -------------------------------------------------------------------------------- /examples/simple/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | angular 5 | 6 | 7 | .module('app', ['angularFileUpload']) 8 | 9 | 10 | .controller('AppController', ['$scope', 'FileUploader', function($scope, FileUploader) { 11 | var uploader = $scope.uploader = new FileUploader({ 12 | url: 'upload.php' 13 | //,timeout: 2000 14 | }); 15 | 16 | // FILTERS 17 | 18 | // a sync filter 19 | uploader.filters.push({ 20 | name: 'syncFilter', 21 | fn: function(item /*{File|FileLikeObject}*/, options) { 22 | console.log('syncFilter'); 23 | return this.queue.length < 10; 24 | } 25 | }); 26 | 27 | // an async filter 28 | uploader.filters.push({ 29 | name: 'asyncFilter', 30 | fn: function(item /*{File|FileLikeObject}*/, options, deferred) { 31 | console.log('asyncFilter'); 32 | setTimeout(deferred.resolve, 1e3); 33 | } 34 | }); 35 | 36 | // CALLBACKS 37 | 38 | uploader.onWhenAddingFileFailed = function(item /*{File|FileLikeObject}*/, filter, options) { 39 | console.info('onWhenAddingFileFailed', item, filter, options); 40 | }; 41 | uploader.onAfterAddingFile = function(fileItem) { 42 | console.info('onAfterAddingFile', fileItem); 43 | }; 44 | uploader.onAfterAddingAll = function(addedFileItems) { 45 | console.info('onAfterAddingAll', addedFileItems); 46 | }; 47 | uploader.onBeforeUploadItem = function(item) { 48 | console.info('onBeforeUploadItem', item); 49 | }; 50 | uploader.onProgressItem = function(fileItem, progress) { 51 | console.info('onProgressItem', fileItem, progress); 52 | }; 53 | uploader.onProgressAll = function(progress) { 54 | console.info('onProgressAll', progress); 55 | }; 56 | uploader.onSuccessItem = function(fileItem, response, status, headers) { 57 | console.info('onSuccessItem', fileItem, response, status, headers); 58 | }; 59 | uploader.onErrorItem = function(fileItem, response, status, headers) { 60 | console.info('onErrorItem', fileItem, response, status, headers); 61 | }; 62 | uploader.onCancelItem = function(fileItem, response, status, headers) { 63 | console.info('onCancelItem', fileItem, response, status, headers); 64 | }; 65 | uploader.onCompleteItem = function(fileItem, response, status, headers) { 66 | console.info('onCompleteItem', fileItem, response, status, headers); 67 | }; 68 | 69 | uploader.onTimeoutItem = function(fileItem) { 70 | console.info('onTimeoutItem', fileItem); 71 | }; 72 | 73 | uploader.onCompleteAll = function() { 74 | console.info('onCompleteAll'); 75 | }; 76 | 77 | console.info('uploader', uploader); 78 | }]); 79 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Simple example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 55 | 56 |
57 | 58 |
59 | 60 |

Select files

61 | 62 |
63 | 64 |
65 | Base drop zone 66 |
67 | 68 | 69 |
70 |
71 | Another drop zone with its own settings 72 |
73 |
74 |
75 | 76 | 77 | Multiple 78 |
79 | 80 | Single 81 |
82 | 83 | Set timeout 2s 84 | 85 |
86 | 87 |
88 | 89 |

Upload queue

90 |

Queue length: {{ uploader.queue.length }}

91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 111 | 116 | 127 | 128 | 129 |
NameSizeProgressStatusActions
{{ item.file.name }}{{ item.file.size/1024/1024|number:2 }} MB 107 |
108 |
109 |
110 |
112 | 113 | 114 | 115 | 117 | 120 | 123 | 126 |
130 | 131 |
132 |
133 | Queue progress: 134 |
135 |
136 |
137 |
138 | 141 | 144 | 147 |
148 | 149 |
150 | 151 |
152 | 153 |
154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /examples/simple/upload.php: -------------------------------------------------------------------------------- 1 | 'File transfer completed' ); 11 | $json = json_encode( $answer ); 12 | 13 | echo $json; 14 | 15 | } else { 16 | 17 | echo 'No files'; 18 | 19 | } 20 | 21 | ?> -------------------------------------------------------------------------------- /examples/simple/uploads/gap.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nervgh/angular-file-upload/173520ae510949d77c558888da8087f581409c08/examples/simple/uploads/gap.txt -------------------------------------------------------------------------------- /examples/without-bootstrap/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | angular 5 | 6 | 7 | .module('app', ['angularFileUpload']) 8 | 9 | 10 | .controller('AppController', ['$scope', 'FileUploader', function($scope, FileUploader) { 11 | var uploader = $scope.uploader = new FileUploader({ 12 | url: 'upload.php' 13 | }); 14 | 15 | // FILTERS 16 | 17 | uploader.filters.push({ 18 | name: 'customFilter', 19 | fn: function(item /*{File|FileLikeObject}*/, options) { 20 | return this.queue.length < 10; 21 | } 22 | }); 23 | 24 | // CALLBACKS 25 | 26 | uploader.onWhenAddingFileFailed = function(item /*{File|FileLikeObject}*/, filter, options) { 27 | console.info('onWhenAddingFileFailed', item, filter, options); 28 | }; 29 | uploader.onAfterAddingFile = function(fileItem) { 30 | console.info('onAfterAddingFile', fileItem); 31 | }; 32 | uploader.onAfterAddingAll = function(addedFileItems) { 33 | console.info('onAfterAddingAll', addedFileItems); 34 | }; 35 | uploader.onBeforeUploadItem = function(item) { 36 | console.info('onBeforeUploadItem', item); 37 | }; 38 | uploader.onProgressItem = function(fileItem, progress) { 39 | console.info('onProgressItem', fileItem, progress); 40 | }; 41 | uploader.onProgressAll = function(progress) { 42 | console.info('onProgressAll', progress); 43 | }; 44 | uploader.onSuccessItem = function(fileItem, response, status, headers) { 45 | console.info('onSuccessItem', fileItem, response, status, headers); 46 | }; 47 | uploader.onErrorItem = function(fileItem, response, status, headers) { 48 | console.info('onErrorItem', fileItem, response, status, headers); 49 | }; 50 | uploader.onCancelItem = function(fileItem, response, status, headers) { 51 | console.info('onCancelItem', fileItem, response, status, headers); 52 | }; 53 | uploader.onCompleteItem = function(fileItem, response, status, headers) { 54 | console.info('onCompleteItem', fileItem, response, status, headers); 55 | }; 56 | uploader.onCompleteAll = function() { 57 | console.info('onCompleteAll'); 58 | }; 59 | 60 | console.info('uploader', uploader); 61 | 62 | 63 | // ------------------------------- 64 | 65 | 66 | var controller = $scope.controller = { 67 | isImage: function(item) { 68 | var type = '|' + item.type.slice(item.type.lastIndexOf('/') + 1) + '|'; 69 | return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1; 70 | } 71 | }; 72 | }]); -------------------------------------------------------------------------------- /examples/without-bootstrap/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | angular 5 | 6 | 7 | .module('app') 8 | 9 | 10 | // Angular File Upload module does not include this directive 11 | // Only for example 12 | 13 | 14 | /** 15 | * The ng-thumb directive 16 | * @author: nerv 17 | * @version: 0.1.2, 2014-01-09 18 | */ 19 | .directive('ngThumb', ['$window', function($window) { 20 | var helper = { 21 | support: !!($window.FileReader && $window.CanvasRenderingContext2D), 22 | isFile: function(item) { 23 | return angular.isObject(item) && item instanceof $window.File; 24 | }, 25 | isImage: function(file) { 26 | var type = '|' + file.type.slice(file.type.lastIndexOf('/') + 1) + '|'; 27 | return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1; 28 | } 29 | }; 30 | 31 | return { 32 | restrict: 'A', 33 | template: '', 34 | link: function(scope, element, attributes) { 35 | if (!helper.support) return; 36 | 37 | var params = scope.$eval(attributes.ngThumb); 38 | 39 | if (!helper.isFile(params.file)) return; 40 | if (!helper.isImage(params.file)) return; 41 | 42 | var canvas = element.find('canvas'); 43 | var reader = new FileReader(); 44 | 45 | reader.onload = onLoadFile; 46 | reader.readAsDataURL(params.file); 47 | 48 | function onLoadFile(event) { 49 | var img = new Image(); 50 | img.onload = onLoadImage; 51 | img.src = event.target.result; 52 | } 53 | 54 | function onLoadImage() { 55 | var width = params.width || this.width / this.height * params.height; 56 | var height = params.height || this.height / this.width * params.width; 57 | canvas.attr({ width: width, height: height }); 58 | canvas[0].getContext('2d').drawImage(this, 0, 0, width, height); 59 | } 60 | } 61 | }; 62 | }]); 63 | -------------------------------------------------------------------------------- /examples/without-bootstrap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Without bootstrap example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Without bootstrap example

25 | 26 |

Examples

27 | 32 | Download / Repository 33 |
34 |
35 | 36 | 37 |
38 |
39 | Base drop zone indication 40 |
41 | 42 |
43 | One more drop zone with its own settings (and indication) 44 |
45 |
46 |
47 | 48 |
49 | 50 | 51 | 52 | 53 |

The queue. Length: {{ uploader.queue.length }}

54 |
    55 |
  • 56 |
    Name: {{ item.file.name }}
    57 |
    Size: {{ item.file.size/1024/1024|number:2 }} Mb
    58 |
    59 | Progress: {{ item.progress }} 60 |
    61 |
    62 |
    63 |
    64 |
    65 | Thumbnail (only images): 66 | 67 | 68 | 69 | 70 |
    71 | 72 | 73 |
    74 |
    75 | 76 | 77 | 78 |
    79 |
  • 80 |
81 |
82 |
83 | Total progress: {{ uploader.progress }} 84 |
85 |
86 |
87 |
88 | 89 | 90 | 91 |
92 | 93 | -------------------------------------------------------------------------------- /examples/without-bootstrap/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | h1, h2 { 6 | background-color: steelblue; 7 | font-family: Tahoma, serif; 8 | color: white; 9 | padding: 10px; 10 | letter-spacing: 1px; 11 | } 12 | 13 | h1 { 14 | font-size: 1.3em; 15 | } 16 | 17 | h2 { 18 | font-size: 1.2em; 19 | } 20 | 21 | canvas { 22 | background-color: #f3f3f3; 23 | -webkit-box-shadow: 3px 3px 3px 0 #e3e3e3; 24 | -moz-box-shadow: 3px 3px 3px 0 #e3e3e3; 25 | box-shadow: 3px 3px 3px 0 #e3e3e3; 26 | border: 1px solid #c3c3c3; 27 | height: 100px; 28 | margin: 6px 0 0 6px; 29 | } 30 | 31 | .nv-file-over { 32 | background-color: #FFBEA3; 33 | } 34 | 35 | .other-drop-zone { 36 | border: 2px dashed burlywood; 37 | padding: 4px; 38 | height: 100px; 39 | } 40 | 41 | .other-over-zone { 42 | background-color: moccasin; 43 | } 44 | 45 | .bg { 46 | background-color: lightgreen; 47 | } 48 | 49 | .over-zone { 50 | border: 2px dashed lavender; 51 | height: 100px; 52 | padding: 4px; 53 | } 54 | 55 | .item-progress-box { 56 | height: 20px; 57 | margin-top: -20px; 58 | margin-left: 60px; 59 | margin-right: 10px; 60 | } 61 | 62 | .item-progress { 63 | background-color: #90B8DA; 64 | height: 100%; 65 | width: 0; 66 | } 67 | 68 | .total-progress-box { 69 | height: 20px; 70 | margin-top: -20px; 71 | margin-left: 90px; 72 | margin-right: 10px; 73 | } 74 | 75 | .total-progress { 76 | background-color: #90B8DA; 77 | height: 100%; 78 | width: 0; 79 | } 80 | 81 | .box { 82 | margin: 20px; 83 | } 84 | 85 | .progress { 86 | background-color: mediumpurple; 87 | height: 20px; 88 | } 89 | 90 | .uploaded { 91 | background-color: lightgreen; 92 | height: 20px; 93 | width: 100px; 94 | } 95 | 96 | ul > li:nth-child(odd) { 97 | background-color: #f5f5f5; 98 | margin: 2px; 99 | } 100 | 101 | .zone { 102 | width: 49%; 103 | } -------------------------------------------------------------------------------- /examples/without-bootstrap/upload.php: -------------------------------------------------------------------------------- 1 | 'File transfer completed' ); 11 | $json = json_encode( $answer ); 12 | 13 | echo $json; 14 | 15 | } else { 16 | 17 | echo 'No files'; 18 | 19 | } 20 | 21 | ?> -------------------------------------------------------------------------------- /examples/without-bootstrap/uploads/gap.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nervgh/angular-file-upload/173520ae510949d77c558888da8087f581409c08/examples/without-bootstrap/uploads/gap.txt -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // https://github.com/gulpjs/gulp/blob/master/docs/README.md 4 | let gulp = require('gulp'); 5 | // https://github.com/shama/webpack-stream 6 | let webpackStream = require('webpack-stream'); 7 | 8 | let WebpackConfig = require('./WebpackConfig'); 9 | let descriptor = require('./package.json'); 10 | 11 | 12 | let config = new WebpackConfig(descriptor, { 13 | src: './src/', 14 | dist: './dist/' 15 | }); 16 | 17 | 18 | gulp.task( 19 | `${config.name}/build`, 20 | function () { 21 | return gulp 22 | .src(config.path.src) 23 | .pipe(webpackStream(config.get())) 24 | .pipe(gulp.dest(config.path.dist)); 25 | } 26 | ); 27 | 28 | gulp.task( 29 | `${config.name}/watch`, function () { 30 | return gulp 31 | .watch(`${config.path.src}**/*.*`, [ 32 | `${config.name}/build` 33 | ]); 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 nerv. https://github.com/nervgh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-file-upload", 3 | "version": "2.6.1", 4 | "homepage": "https://github.com/nervgh/angular-file-upload", 5 | "description": "Angular File Upload is a module for the AngularJS framework", 6 | "license": "MIT", 7 | "author": { 8 | "name": "nerv", 9 | "url": "https://github.com/nervgh" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/nervgh/angular-file-upload.git" 14 | }, 15 | "main": "dist/angular-file-upload.js", 16 | "engines": { 17 | "node": ">=4.0.0" 18 | }, 19 | "devDependencies": { 20 | "babel-core": "~6.8.0", 21 | "babel-loader": "~6.2.4", 22 | "babel-preset-es2015": "~6.6.0", 23 | "gulp": "~3.9.1", 24 | "json-loader": "~0.5.4", 25 | "raw-loader": "~0.5.1", 26 | "serve": "^11.2.0", 27 | "webpack-stream": "~3.2.0" 28 | }, 29 | "scripts": { 30 | "serve": "serve ." 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularFileUpload" 3 | } -------------------------------------------------------------------------------- /src/directives/FileDrop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | export default function __identity($parse, FileUploader, FileDrop) { 8 | 9 | 10 | return { 11 | link: (scope, element, attributes) => { 12 | var uploader = scope.$eval(attributes.uploader); 13 | 14 | if (!(uploader instanceof FileUploader)) { 15 | throw new TypeError('"Uploader" must be an instance of FileUploader'); 16 | } 17 | 18 | if (!uploader.isHTML5) return; 19 | 20 | var object = new FileDrop({ 21 | uploader: uploader, 22 | element: element 23 | }); 24 | 25 | object.getOptions = $parse(attributes.options).bind(object, scope); 26 | object.getFilters = () => attributes.filters; 27 | } 28 | }; 29 | 30 | 31 | } 32 | 33 | 34 | __identity.$inject = [ 35 | '$parse', 36 | 'FileUploader', 37 | 'FileDrop' 38 | ]; -------------------------------------------------------------------------------- /src/directives/FileOver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | export default function __identity(FileUploader, FileOver) { 8 | 9 | 10 | return { 11 | link: (scope, element, attributes) => { 12 | var uploader = scope.$eval(attributes.uploader); 13 | 14 | if (!(uploader instanceof FileUploader)) { 15 | throw new TypeError('"Uploader" must be an instance of FileUploader'); 16 | } 17 | 18 | var object = new FileOver({ 19 | uploader: uploader, 20 | element: element 21 | }); 22 | 23 | object.getOverClass = () => attributes.overClass || object.overClass; 24 | } 25 | }; 26 | 27 | 28 | } 29 | 30 | 31 | __identity.$inject = [ 32 | 'FileUploader', 33 | 'FileOver' 34 | ]; -------------------------------------------------------------------------------- /src/directives/FileSelect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | export default function __identity($parse, FileUploader, FileSelect) { 8 | 9 | 10 | return { 11 | link: (scope, element, attributes) => { 12 | var uploader = scope.$eval(attributes.uploader); 13 | 14 | if (!(uploader instanceof FileUploader)) { 15 | throw new TypeError('"Uploader" must be an instance of FileUploader'); 16 | } 17 | 18 | var object = new FileSelect({ 19 | uploader: uploader, 20 | element: element, 21 | scope: scope 22 | }); 23 | 24 | object.getOptions = $parse(attributes.options).bind(object, scope); 25 | object.getFilters = () => attributes.filters; 26 | } 27 | }; 28 | 29 | 30 | } 31 | 32 | 33 | __identity.$inject = [ 34 | '$parse', 35 | 'FileUploader', 36 | 'FileSelect' 37 | ]; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './config.json'; 5 | 6 | 7 | import options from './values/options' 8 | 9 | 10 | import serviceFileUploader from './services/FileUploader'; 11 | import serviceFileLikeObject from './services/FileLikeObject'; 12 | import serviceFileItem from './services/FileItem'; 13 | import serviceFileDirective from './services/FileDirective'; 14 | import serviceFileSelect from './services/FileSelect'; 15 | import servicePipeline from './services/Pipeline'; 16 | import serviceFileDrop from './services/FileDrop'; 17 | import serviceFileOver from './services/FileOver'; 18 | 19 | 20 | import directiveFileSelect from './directives/FileSelect'; 21 | import directiveFileDrop from './directives/FileDrop'; 22 | import directiveFileOver from './directives/FileOver'; 23 | 24 | 25 | angular 26 | .module(CONFIG.name, []) 27 | .value('fileUploaderOptions', options) 28 | .factory('FileUploader', serviceFileUploader) 29 | .factory('FileLikeObject', serviceFileLikeObject) 30 | .factory('FileItem', serviceFileItem) 31 | .factory('FileDirective', serviceFileDirective) 32 | .factory('FileSelect', serviceFileSelect) 33 | .factory('FileDrop', serviceFileDrop) 34 | .factory('FileOver', serviceFileOver) 35 | .factory('Pipeline', servicePipeline) 36 | .directive('nvFileSelect', directiveFileSelect) 37 | .directive('nvFileDrop', directiveFileDrop) 38 | .directive('nvFileOver', directiveFileOver) 39 | .run([ 40 | 'FileUploader', 41 | 'FileLikeObject', 42 | 'FileItem', 43 | 'FileDirective', 44 | 'FileSelect', 45 | 'FileDrop', 46 | 'FileOver', 47 | 'Pipeline', 48 | function(FileUploader, FileLikeObject, FileItem, FileDirective, FileSelect, FileDrop, FileOver, Pipeline) { 49 | // only for compatibility 50 | FileUploader.FileLikeObject = FileLikeObject; 51 | FileUploader.FileItem = FileItem; 52 | FileUploader.FileDirective = FileDirective; 53 | FileUploader.FileSelect = FileSelect; 54 | FileUploader.FileDrop = FileDrop; 55 | FileUploader.FileOver = FileOver; 56 | FileUploader.Pipeline = Pipeline; 57 | } 58 | ]); 59 | -------------------------------------------------------------------------------- /src/services/FileDirective.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | let { 8 | extend 9 | } = angular; 10 | 11 | 12 | export default function __identity() { 13 | 14 | 15 | class FileDirective { 16 | /** 17 | * Creates instance of {FileDirective} object 18 | * @param {Object} options 19 | * @param {Object} options.uploader 20 | * @param {HTMLElement} options.element 21 | * @param {Object} options.events 22 | * @param {String} options.prop 23 | * @constructor 24 | */ 25 | constructor(options) { 26 | extend(this, options); 27 | this.uploader._directives[this.prop].push(this); 28 | this._saveLinks(); 29 | this.bind(); 30 | } 31 | /** 32 | * Binds events handles 33 | */ 34 | bind() { 35 | for(var key in this.events) { 36 | var prop = this.events[key]; 37 | this.element.bind(key, this[prop]); 38 | } 39 | } 40 | /** 41 | * Unbinds events handles 42 | */ 43 | unbind() { 44 | for(var key in this.events) { 45 | this.element.unbind(key, this.events[key]); 46 | } 47 | } 48 | /** 49 | * Destroys directive 50 | */ 51 | destroy() { 52 | var index = this.uploader._directives[this.prop].indexOf(this); 53 | this.uploader._directives[this.prop].splice(index, 1); 54 | this.unbind(); 55 | // this.element = null; 56 | } 57 | /** 58 | * Saves links to functions 59 | * @private 60 | */ 61 | _saveLinks() { 62 | for(var key in this.events) { 63 | var prop = this.events[key]; 64 | this[prop] = this[prop].bind(this); 65 | } 66 | } 67 | } 68 | 69 | 70 | /** 71 | * Map of events 72 | * @type {Object} 73 | */ 74 | FileDirective.prototype.events = {}; 75 | 76 | 77 | return FileDirective; 78 | } -------------------------------------------------------------------------------- /src/services/FileDrop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | let { 8 | extend, 9 | forEach 10 | } = angular; 11 | 12 | 13 | export default function __identity(FileDirective) { 14 | 15 | 16 | return class FileDrop extends FileDirective { 17 | /** 18 | * Creates instance of {FileDrop} object 19 | * @param {Object} options 20 | * @constructor 21 | */ 22 | constructor(options) { 23 | let extendedOptions = extend(options, { 24 | // Map of events 25 | events: { 26 | $destroy: 'destroy', 27 | drop: 'onDrop', 28 | dragover: 'onDragOver', 29 | dragleave: 'onDragLeave' 30 | }, 31 | // Name of property inside uploader._directive object 32 | prop: 'drop' 33 | }); 34 | 35 | super(extendedOptions); 36 | } 37 | /** 38 | * Returns options 39 | * @return {Object|undefined} 40 | */ 41 | getOptions() { 42 | } 43 | /** 44 | * Returns filters 45 | * @return {Array|String|undefined} 46 | */ 47 | getFilters() { 48 | } 49 | /** 50 | * Event handler 51 | */ 52 | onDrop(event) { 53 | var transfer = this._getTransfer(event); 54 | if(!transfer) return; 55 | var options = this.getOptions(); 56 | var filters = this.getFilters(); 57 | this._preventAndStop(event); 58 | forEach(this.uploader._directives.over, this._removeOverClass, this); 59 | this.uploader.addToQueue(transfer.files, options, filters); 60 | } 61 | /** 62 | * Event handler 63 | */ 64 | onDragOver(event) { 65 | var transfer = this._getTransfer(event); 66 | if(!this._haveFiles(transfer.types)) return; 67 | transfer.dropEffect = 'copy'; 68 | this._preventAndStop(event); 69 | forEach(this.uploader._directives.over, this._addOverClass, this); 70 | } 71 | /** 72 | * Event handler 73 | */ 74 | onDragLeave(event) { 75 | if(event.currentTarget === this.element[0]) return; 76 | this._preventAndStop(event); 77 | forEach(this.uploader._directives.over, this._removeOverClass, this); 78 | } 79 | /** 80 | * Helper 81 | */ 82 | _getTransfer(event) { 83 | return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix; 84 | } 85 | /** 86 | * Helper 87 | */ 88 | _preventAndStop(event) { 89 | event.preventDefault(); 90 | event.stopPropagation(); 91 | } 92 | /** 93 | * Returns "true" if types contains files 94 | * @param {Object} types 95 | */ 96 | _haveFiles(types) { 97 | if(!types) return false; 98 | if(types.indexOf) { 99 | return types.indexOf('Files') !== -1; 100 | } else if(types.contains) { 101 | return types.contains('Files'); 102 | } else { 103 | return false; 104 | } 105 | } 106 | /** 107 | * Callback 108 | */ 109 | _addOverClass(item) { 110 | item.addOverClass(); 111 | } 112 | /** 113 | * Callback 114 | */ 115 | _removeOverClass(item) { 116 | item.removeOverClass(); 117 | } 118 | } 119 | } 120 | 121 | 122 | __identity.$inject = [ 123 | 'FileDirective' 124 | ]; -------------------------------------------------------------------------------- /src/services/FileItem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | let { 8 | copy, 9 | extend, 10 | element, 11 | isElement 12 | } = angular; 13 | 14 | 15 | export default function __identity($compile, FileLikeObject) { 16 | 17 | 18 | return class FileItem { 19 | /** 20 | * Creates an instance of FileItem 21 | * @param {FileUploader} uploader 22 | * @param {File|HTMLInputElement|Object} some 23 | * @param {Object} options 24 | * @constructor 25 | */ 26 | constructor(uploader, some, options) { 27 | var isInput = !!some.input; 28 | var input = isInput ? element(some.input) : null; 29 | var file = !isInput ? some : null; 30 | 31 | extend(this, { 32 | url: uploader.url, 33 | alias: uploader.alias, 34 | headers: copy(uploader.headers), 35 | formData: copy(uploader.formData), 36 | removeAfterUpload: uploader.removeAfterUpload, 37 | withCredentials: uploader.withCredentials, 38 | disableMultipart: uploader.disableMultipart, 39 | method: uploader.method, 40 | timeout: uploader.timeout 41 | }, options, { 42 | uploader: uploader, 43 | file: new FileLikeObject(some), 44 | isReady: false, 45 | isUploading: false, 46 | isUploaded: false, 47 | isSuccess: false, 48 | isCancel: false, 49 | isError: false, 50 | progress: 0, 51 | index: null, 52 | _file: file, 53 | _input: input 54 | }); 55 | 56 | if (input) this._replaceNode(input); 57 | } 58 | /********************** 59 | * PUBLIC 60 | **********************/ 61 | /** 62 | * Uploads a FileItem 63 | */ 64 | upload() { 65 | try { 66 | this.uploader.uploadItem(this); 67 | } catch(e) { 68 | var message = e.name + ':' + e.message; 69 | this.uploader._onCompleteItem(this, message, e.code, []); 70 | this.uploader._onErrorItem(this, message, e.code, []); 71 | } 72 | } 73 | /** 74 | * Cancels uploading of FileItem 75 | */ 76 | cancel() { 77 | this.uploader.cancelItem(this); 78 | } 79 | /** 80 | * Removes a FileItem 81 | */ 82 | remove() { 83 | this.uploader.removeFromQueue(this); 84 | } 85 | /** 86 | * Callback 87 | * @private 88 | */ 89 | onBeforeUpload() { 90 | } 91 | /** 92 | * Callback 93 | * @param {Number} progress 94 | * @private 95 | */ 96 | onProgress(progress) { 97 | } 98 | /** 99 | * Callback 100 | * @param {*} response 101 | * @param {Number} status 102 | * @param {Object} headers 103 | */ 104 | onSuccess(response, status, headers) { 105 | } 106 | /** 107 | * Callback 108 | * @param {*} response 109 | * @param {Number} status 110 | * @param {Object} headers 111 | */ 112 | onError(response, status, headers) { 113 | } 114 | /** 115 | * Callback 116 | * @param {*} response 117 | * @param {Number} status 118 | * @param {Object} headers 119 | */ 120 | onCancel(response, status, headers) { 121 | } 122 | /** 123 | * Callback 124 | * @param {*} response 125 | * @param {Number} status 126 | * @param {Object} headers 127 | */ 128 | onComplete(response, status, headers) { 129 | } 130 | /** 131 | * Callback 132 | */ 133 | onTimeout() { 134 | } 135 | /********************** 136 | * PRIVATE 137 | **********************/ 138 | /** 139 | * Inner callback 140 | */ 141 | _onBeforeUpload() { 142 | this.isReady = true; 143 | this.isUploading = false; 144 | this.isUploaded = false; 145 | this.isSuccess = false; 146 | this.isCancel = false; 147 | this.isError = false; 148 | this.progress = 0; 149 | this.onBeforeUpload(); 150 | } 151 | /** 152 | * Inner callback 153 | * @param {Number} progress 154 | * @private 155 | */ 156 | _onProgress(progress) { 157 | this.progress = progress; 158 | this.onProgress(progress); 159 | } 160 | /** 161 | * Inner callback 162 | * @param {*} response 163 | * @param {Number} status 164 | * @param {Object} headers 165 | * @private 166 | */ 167 | _onSuccess(response, status, headers) { 168 | this.isReady = false; 169 | this.isUploading = false; 170 | this.isUploaded = true; 171 | this.isSuccess = true; 172 | this.isCancel = false; 173 | this.isError = false; 174 | this.progress = 100; 175 | this.index = null; 176 | this.onSuccess(response, status, headers); 177 | } 178 | /** 179 | * Inner callback 180 | * @param {*} response 181 | * @param {Number} status 182 | * @param {Object} headers 183 | * @private 184 | */ 185 | _onError(response, status, headers) { 186 | this.isReady = false; 187 | this.isUploading = false; 188 | this.isUploaded = true; 189 | this.isSuccess = false; 190 | this.isCancel = false; 191 | this.isError = true; 192 | this.progress = 0; 193 | this.index = null; 194 | this.onError(response, status, headers); 195 | } 196 | /** 197 | * Inner callback 198 | * @param {*} response 199 | * @param {Number} status 200 | * @param {Object} headers 201 | * @private 202 | */ 203 | _onCancel(response, status, headers) { 204 | this.isReady = false; 205 | this.isUploading = false; 206 | this.isUploaded = false; 207 | this.isSuccess = false; 208 | this.isCancel = true; 209 | this.isError = false; 210 | this.progress = 0; 211 | this.index = null; 212 | this.onCancel(response, status, headers); 213 | } 214 | /** 215 | * Inner callback 216 | * @param {*} response 217 | * @param {Number} status 218 | * @param {Object} headers 219 | * @private 220 | */ 221 | _onComplete(response, status, headers) { 222 | this.onComplete(response, status, headers); 223 | if(this.removeAfterUpload) this.remove(); 224 | } 225 | /** 226 | * Inner callback 227 | * @private 228 | */ 229 | _onTimeout() { 230 | this.isReady = false; 231 | this.isUploading = false; 232 | this.isUploaded = false; 233 | this.isSuccess = false; 234 | this.isCancel = false; 235 | this.isError = true; 236 | this.progress = 0; 237 | this.index = null; 238 | this.onTimeout(); 239 | } 240 | /** 241 | * Destroys a FileItem 242 | */ 243 | _destroy() { 244 | if(this._input) this._input.remove(); 245 | if(this._form) this._form.remove(); 246 | delete this._form; 247 | delete this._input; 248 | } 249 | /** 250 | * Prepares to uploading 251 | * @private 252 | */ 253 | _prepareToUploading() { 254 | this.index = this.index || ++this.uploader._nextIndex; 255 | this.isReady = true; 256 | } 257 | /** 258 | * Replaces input element on his clone 259 | * @param {JQLite|jQuery} input 260 | * @private 261 | */ 262 | _replaceNode(input) { 263 | var clone = $compile(input.clone())(input.scope()); 264 | clone.prop('value', null); // FF fix 265 | input.css('display', 'none'); 266 | input.after(clone); // remove jquery dependency 267 | } 268 | } 269 | } 270 | 271 | 272 | __identity.$inject = [ 273 | '$compile', 274 | 'FileLikeObject' 275 | ]; 276 | -------------------------------------------------------------------------------- /src/services/FileLikeObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | let { 8 | copy, 9 | isElement, 10 | isString 11 | } = angular; 12 | 13 | 14 | export default function __identity() { 15 | 16 | 17 | return class FileLikeObject { 18 | /** 19 | * Creates an instance of FileLikeObject 20 | * @param {File|HTMLInputElement|Object} fileOrInput 21 | * @constructor 22 | */ 23 | constructor(fileOrInput) { 24 | var isInput = isElement(fileOrInput); 25 | var fakePathOrObject = isInput ? fileOrInput.value : fileOrInput; 26 | var postfix = isString(fakePathOrObject) ? 'FakePath' : 'Object'; 27 | var method = '_createFrom' + postfix; 28 | this[method](fakePathOrObject, fileOrInput); 29 | } 30 | /** 31 | * Creates file like object from fake path string 32 | * @param {String} path 33 | * @private 34 | */ 35 | _createFromFakePath(path, input) { 36 | this.lastModifiedDate = null; 37 | this.size = null; 38 | this.type = 'like/' + path.slice(path.lastIndexOf('.') + 1).toLowerCase(); 39 | this.name = path.slice(path.lastIndexOf('/') + path.lastIndexOf('\\') + 2); 40 | this.input = input; 41 | } 42 | /** 43 | * Creates file like object from object 44 | * @param {File|FileLikeObject} object 45 | * @private 46 | */ 47 | _createFromObject(object) { 48 | this.lastModifiedDate = copy(object.lastModifiedDate); 49 | this.size = object.size; 50 | this.type = object.type; 51 | this.name = object.name; 52 | this.input = object.input; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/services/FileOver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | let { 8 | extend 9 | } = angular; 10 | 11 | 12 | export default function __identity(FileDirective) { 13 | 14 | 15 | return class FileOver extends FileDirective { 16 | /** 17 | * Creates instance of {FileDrop} object 18 | * @param {Object} options 19 | * @constructor 20 | */ 21 | constructor(options) { 22 | let extendedOptions = extend(options, { 23 | // Map of events 24 | events: { 25 | $destroy: 'destroy' 26 | }, 27 | // Name of property inside uploader._directive object 28 | prop: 'over', 29 | // Over class 30 | overClass: 'nv-file-over' 31 | }); 32 | 33 | super(extendedOptions); 34 | } 35 | /** 36 | * Adds over class 37 | */ 38 | addOverClass() { 39 | this.element.addClass(this.getOverClass()); 40 | } 41 | /** 42 | * Removes over class 43 | */ 44 | removeOverClass() { 45 | this.element.removeClass(this.getOverClass()); 46 | } 47 | /** 48 | * Returns over class 49 | * @returns {String} 50 | */ 51 | getOverClass() { 52 | return this.overClass; 53 | } 54 | } 55 | } 56 | 57 | 58 | __identity.$inject = [ 59 | 'FileDirective' 60 | ]; -------------------------------------------------------------------------------- /src/services/FileSelect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | let { 8 | extend 9 | } = angular; 10 | 11 | 12 | export default function __identity($compile, FileDirective) { 13 | 14 | 15 | return class FileSelect extends FileDirective { 16 | /** 17 | * Creates instance of {FileSelect} object 18 | * @param {Object} options 19 | * @constructor 20 | */ 21 | constructor(options) { 22 | let extendedOptions = extend(options, { 23 | // Map of events 24 | events: { 25 | $destroy: 'destroy', 26 | change: 'onChange' 27 | }, 28 | // Name of property inside uploader._directive object 29 | prop: 'select' 30 | }); 31 | 32 | super(extendedOptions); 33 | 34 | if(!this.uploader.isHTML5) { 35 | this.element.removeAttr('multiple'); 36 | } 37 | this.element.prop('value', null); // FF fix 38 | } 39 | /** 40 | * Returns options 41 | * @return {Object|undefined} 42 | */ 43 | getOptions() { 44 | } 45 | /** 46 | * Returns filters 47 | * @return {Array|String|undefined} 48 | */ 49 | getFilters() { 50 | } 51 | /** 52 | * If returns "true" then HTMLInputElement will be cleared 53 | * @returns {Boolean} 54 | */ 55 | isEmptyAfterSelection() { 56 | return !!this.element.attr('multiple'); 57 | } 58 | /** 59 | * Event handler 60 | */ 61 | onChange() { 62 | var files = this.uploader.isHTML5 ? this.element[0].files : this.element[0]; 63 | var options = this.getOptions(); 64 | var filters = this.getFilters(); 65 | 66 | if(!this.uploader.isHTML5) this.destroy(); 67 | this.uploader.addToQueue(files, options, filters); 68 | if(this.isEmptyAfterSelection()) { 69 | this.element.prop('value', null); 70 | this.element.replaceWith($compile(this.element.clone())(this.scope)); // IE fix 71 | } 72 | } 73 | } 74 | } 75 | 76 | 77 | __identity.$inject = [ 78 | '$compile', 79 | 'FileDirective' 80 | ]; 81 | -------------------------------------------------------------------------------- /src/services/FileUploader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | import CONFIG from './../config.json'; 5 | 6 | 7 | let { 8 | bind, 9 | copy, 10 | extend, 11 | forEach, 12 | isObject, 13 | isNumber, 14 | isDefined, 15 | isArray, 16 | isUndefined, 17 | element 18 | } = angular; 19 | 20 | 21 | export default function __identity(fileUploaderOptions, $rootScope, $http, $window, $timeout, FileLikeObject, FileItem, Pipeline) { 22 | 23 | 24 | let { 25 | File, 26 | FormData 27 | } = $window; 28 | 29 | 30 | class FileUploader { 31 | /********************** 32 | * PUBLIC 33 | **********************/ 34 | /** 35 | * Creates an instance of FileUploader 36 | * @param {Object} [options] 37 | * @constructor 38 | */ 39 | constructor(options) { 40 | var settings = copy(fileUploaderOptions); 41 | 42 | extend(this, settings, options, { 43 | isUploading: false, 44 | _nextIndex: 0, 45 | _directives: {select: [], drop: [], over: []} 46 | }); 47 | 48 | // add default filters 49 | this.filters.unshift({name: 'queueLimit', fn: this._queueLimitFilter}); 50 | this.filters.unshift({name: 'folder', fn: this._folderFilter}); 51 | } 52 | /** 53 | * Adds items to the queue 54 | * @param {File|HTMLInputElement|Object|FileList|Array} files 55 | * @param {Object} [options] 56 | * @param {Array|String} filters 57 | */ 58 | addToQueue(files, options, filters) { 59 | let incomingQueue = this.isArrayLikeObject(files) ? Array.prototype.slice.call(files): [files]; 60 | var arrayOfFilters = this._getFilters(filters); 61 | var count = this.queue.length; 62 | var addedFileItems = []; 63 | 64 | let next = () => { 65 | let something = incomingQueue.shift(); 66 | 67 | if (isUndefined(something)) { 68 | return done(); 69 | } 70 | 71 | let fileLikeObject = this.isFile(something) ? something : new FileLikeObject(something); 72 | let pipes = this._convertFiltersToPipes(arrayOfFilters); 73 | let pipeline = new Pipeline(pipes); 74 | let onThrown = (err) => { 75 | let {originalFilter} = err.pipe; 76 | let [fileLikeObject, options] = err.args; 77 | this._onWhenAddingFileFailed(fileLikeObject, originalFilter, options); 78 | next(); 79 | }; 80 | let onSuccessful = (fileLikeObject, options) => { 81 | let fileItem = new FileItem(this, fileLikeObject, options); 82 | addedFileItems.push(fileItem); 83 | this.queue.push(fileItem); 84 | this._onAfterAddingFile(fileItem); 85 | next(); 86 | }; 87 | pipeline.onThrown = onThrown; 88 | pipeline.onSuccessful = onSuccessful; 89 | pipeline.exec(fileLikeObject, options); 90 | }; 91 | 92 | let done = () => { 93 | if(this.queue.length !== count) { 94 | this._onAfterAddingAll(addedFileItems); 95 | this.progress = this._getTotalProgress(); 96 | } 97 | 98 | this._render(); 99 | if (this.autoUpload) this.uploadAll(); 100 | }; 101 | 102 | next(); 103 | } 104 | /** 105 | * Remove items from the queue. Remove last: index = -1 106 | * @param {FileItem|Number} value 107 | */ 108 | removeFromQueue(value) { 109 | var index = this.getIndexOfItem(value); 110 | var item = this.queue[index]; 111 | if(item.isUploading) item.cancel(); 112 | this.queue.splice(index, 1); 113 | item._destroy(); 114 | this.progress = this._getTotalProgress(); 115 | } 116 | /** 117 | * Clears the queue 118 | */ 119 | clearQueue() { 120 | while(this.queue.length) { 121 | this.queue[0].remove(); 122 | } 123 | this.progress = 0; 124 | } 125 | /** 126 | * Uploads a item from the queue 127 | * @param {FileItem|Number} value 128 | */ 129 | uploadItem(value) { 130 | var index = this.getIndexOfItem(value); 131 | var item = this.queue[index]; 132 | var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport'; 133 | 134 | item._prepareToUploading(); 135 | if(this.isUploading) return; 136 | 137 | this._onBeforeUploadItem(item); 138 | if (item.isCancel) return; 139 | 140 | item.isUploading = true; 141 | this.isUploading = true; 142 | this[transport](item); 143 | this._render(); 144 | } 145 | /** 146 | * Cancels uploading of item from the queue 147 | * @param {FileItem|Number} value 148 | */ 149 | cancelItem(value) { 150 | var index = this.getIndexOfItem(value); 151 | var item = this.queue[index]; 152 | var prop = this.isHTML5 ? '_xhr' : '_form'; 153 | if (!item) return; 154 | item.isCancel = true; 155 | if(item.isUploading) { 156 | // It will call this._onCancelItem() & this._onCompleteItem() asynchronously 157 | item[prop].abort(); 158 | } else { 159 | let dummy = [undefined, 0, {}]; 160 | let onNextTick = () => { 161 | this._onCancelItem(item, ...dummy); 162 | this._onCompleteItem(item, ...dummy); 163 | }; 164 | $timeout(onNextTick); // Trigger callbacks asynchronously (setImmediate emulation) 165 | } 166 | } 167 | /** 168 | * Uploads all not uploaded items of queue 169 | */ 170 | uploadAll() { 171 | var items = this.getNotUploadedItems().filter(item => !item.isUploading); 172 | if(!items.length) return; 173 | 174 | forEach(items, item => item._prepareToUploading()); 175 | items[0].upload(); 176 | } 177 | /** 178 | * Cancels all uploads 179 | */ 180 | cancelAll() { 181 | var items = this.getNotUploadedItems(); 182 | forEach(items, item => item.cancel()); 183 | } 184 | /** 185 | * Returns "true" if value an instance of File 186 | * @param {*} value 187 | * @returns {Boolean} 188 | * @private 189 | */ 190 | isFile(value) { 191 | return this.constructor.isFile(value); 192 | } 193 | /** 194 | * Returns "true" if value an instance of FileLikeObject 195 | * @param {*} value 196 | * @returns {Boolean} 197 | * @private 198 | */ 199 | isFileLikeObject(value) { 200 | return this.constructor.isFileLikeObject(value); 201 | } 202 | /** 203 | * Returns "true" if value is array like object 204 | * @param {*} value 205 | * @returns {Boolean} 206 | */ 207 | isArrayLikeObject(value) { 208 | return this.constructor.isArrayLikeObject(value); 209 | } 210 | /** 211 | * Returns a index of item from the queue 212 | * @param {Item|Number} value 213 | * @returns {Number} 214 | */ 215 | getIndexOfItem(value) { 216 | return isNumber(value) ? value : this.queue.indexOf(value); 217 | } 218 | /** 219 | * Returns not uploaded items 220 | * @returns {Array} 221 | */ 222 | getNotUploadedItems() { 223 | return this.queue.filter(item => !item.isUploaded); 224 | } 225 | /** 226 | * Returns items ready for upload 227 | * @returns {Array} 228 | */ 229 | getReadyItems() { 230 | return this.queue 231 | .filter(item => (item.isReady && !item.isUploading)) 232 | .sort((item1, item2) => item1.index - item2.index); 233 | } 234 | /** 235 | * Destroys instance of FileUploader 236 | */ 237 | destroy() { 238 | forEach(this._directives, (key) => { 239 | forEach(this._directives[key], (object) => { 240 | object.destroy(); 241 | }); 242 | }); 243 | } 244 | /** 245 | * Callback 246 | * @param {Array} fileItems 247 | */ 248 | onAfterAddingAll(fileItems) { 249 | } 250 | /** 251 | * Callback 252 | * @param {FileItem} fileItem 253 | */ 254 | onAfterAddingFile(fileItem) { 255 | } 256 | /** 257 | * Callback 258 | * @param {File|Object} item 259 | * @param {Object} filter 260 | * @param {Object} options 261 | */ 262 | onWhenAddingFileFailed(item, filter, options) { 263 | } 264 | /** 265 | * Callback 266 | * @param {FileItem} fileItem 267 | */ 268 | onBeforeUploadItem(fileItem) { 269 | } 270 | /** 271 | * Callback 272 | * @param {FileItem} fileItem 273 | * @param {Number} progress 274 | */ 275 | onProgressItem(fileItem, progress) { 276 | } 277 | /** 278 | * Callback 279 | * @param {Number} progress 280 | */ 281 | onProgressAll(progress) { 282 | } 283 | /** 284 | * Callback 285 | * @param {FileItem} item 286 | * @param {*} response 287 | * @param {Number} status 288 | * @param {Object} headers 289 | */ 290 | onSuccessItem(item, response, status, headers) { 291 | } 292 | /** 293 | * Callback 294 | * @param {FileItem} item 295 | * @param {*} response 296 | * @param {Number} status 297 | * @param {Object} headers 298 | */ 299 | onErrorItem(item, response, status, headers) { 300 | } 301 | /** 302 | * Callback 303 | * @param {FileItem} item 304 | * @param {*} response 305 | * @param {Number} status 306 | * @param {Object} headers 307 | */ 308 | onCancelItem(item, response, status, headers) { 309 | } 310 | /** 311 | * Callback 312 | * @param {FileItem} item 313 | * @param {*} response 314 | * @param {Number} status 315 | * @param {Object} headers 316 | */ 317 | onCompleteItem(item, response, status, headers) { 318 | } 319 | /** 320 | * Callback 321 | * @param {FileItem} item 322 | */ 323 | onTimeoutItem(item) { 324 | } 325 | /** 326 | * Callback 327 | */ 328 | onCompleteAll() { 329 | } 330 | /********************** 331 | * PRIVATE 332 | **********************/ 333 | /** 334 | * Returns the total progress 335 | * @param {Number} [value] 336 | * @returns {Number} 337 | * @private 338 | */ 339 | _getTotalProgress(value) { 340 | if(this.removeAfterUpload) return value || 0; 341 | 342 | var notUploaded = this.getNotUploadedItems().length; 343 | var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length; 344 | var ratio = 100 / this.queue.length; 345 | var current = (value || 0) * ratio / 100; 346 | 347 | return Math.round(uploaded * ratio + current); 348 | } 349 | /** 350 | * Returns array of filters 351 | * @param {Array|String} filters 352 | * @returns {Array} 353 | * @private 354 | */ 355 | _getFilters(filters) { 356 | if(!filters) return this.filters; 357 | if(isArray(filters)) return filters; 358 | var names = filters.match(/[^\s,]+/g); 359 | return this.filters 360 | .filter(filter => names.indexOf(filter.name) !== -1); 361 | } 362 | /** 363 | * @param {Array} filters 364 | * @returns {Array} 365 | * @private 366 | */ 367 | _convertFiltersToPipes(filters) { 368 | return filters 369 | .map(filter => { 370 | let fn = bind(this, filter.fn); 371 | fn.isAsync = filter.fn.length === 3; 372 | fn.originalFilter = filter; 373 | return fn; 374 | }); 375 | } 376 | /** 377 | * Updates html 378 | * @private 379 | */ 380 | _render() { 381 | if(!$rootScope.$$phase) $rootScope.$apply(); 382 | } 383 | /** 384 | * Returns "true" if item is a file (not folder) 385 | * @param {File|FileLikeObject} item 386 | * @returns {Boolean} 387 | * @private 388 | */ 389 | _folderFilter(item) { 390 | return !!(item.size || item.type); 391 | } 392 | /** 393 | * Returns "true" if the limit has not been reached 394 | * @returns {Boolean} 395 | * @private 396 | */ 397 | _queueLimitFilter() { 398 | return this.queue.length < this.queueLimit; 399 | } 400 | /** 401 | * Checks whether upload successful 402 | * @param {Number} status 403 | * @returns {Boolean} 404 | * @private 405 | */ 406 | _isSuccessCode(status) { 407 | return (status >= 200 && status < 300) || status === 304; 408 | } 409 | /** 410 | * Transforms the server response 411 | * @param {*} response 412 | * @param {Object} headers 413 | * @returns {*} 414 | * @private 415 | */ 416 | _transformResponse(response, headers) { 417 | var headersGetter = this._headersGetter(headers); 418 | forEach($http.defaults.transformResponse, (transformFn) => { 419 | response = transformFn(response, headersGetter); 420 | }); 421 | return response; 422 | } 423 | /** 424 | * Parsed response headers 425 | * @param headers 426 | * @returns {Object} 427 | * @see https://github.com/angular/angular.js/blob/master/src/ng/http.js 428 | * @private 429 | */ 430 | _parseHeaders(headers) { 431 | var parsed = {}, key, val, i; 432 | 433 | if(!headers) return parsed; 434 | 435 | forEach(headers.split('\n'), (line) => { 436 | i = line.indexOf(':'); 437 | key = line.slice(0, i).trim().toLowerCase(); 438 | val = line.slice(i + 1).trim(); 439 | 440 | if(key) { 441 | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 442 | } 443 | }); 444 | 445 | return parsed; 446 | } 447 | /** 448 | * Returns function that returns headers 449 | * @param {Object} parsedHeaders 450 | * @returns {Function} 451 | * @private 452 | */ 453 | _headersGetter(parsedHeaders) { 454 | return (name) => { 455 | if(name) { 456 | return parsedHeaders[name.toLowerCase()] || null; 457 | } 458 | return parsedHeaders; 459 | }; 460 | } 461 | /** 462 | * The XMLHttpRequest transport 463 | * @param {FileItem} item 464 | * @private 465 | */ 466 | _xhrTransport(item) { 467 | var xhr = item._xhr = new XMLHttpRequest(); 468 | var sendable; 469 | 470 | if (!item.disableMultipart) { 471 | sendable = new FormData(); 472 | forEach(item.formData, (obj) => { 473 | forEach(obj, (value, key) => { 474 | sendable.append(key, value); 475 | }); 476 | }); 477 | 478 | sendable.append(item.alias, item._file, item.file.name); 479 | } 480 | else { 481 | sendable = item._file; 482 | } 483 | 484 | if(typeof(item._file.size) != 'number') { 485 | throw new TypeError('The file specified is no longer valid'); 486 | } 487 | 488 | xhr.upload.onprogress = (event) => { 489 | var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0); 490 | this._onProgressItem(item, progress); 491 | }; 492 | 493 | xhr.onload = () => { 494 | var headers = this._parseHeaders(xhr.getAllResponseHeaders()); 495 | var response = this._transformResponse(xhr.response, headers); 496 | var gist = this._isSuccessCode(xhr.status) ? 'Success' : 'Error'; 497 | var method = '_on' + gist + 'Item'; 498 | this[method](item, response, xhr.status, headers); 499 | this._onCompleteItem(item, response, xhr.status, headers); 500 | }; 501 | 502 | xhr.onerror = () => { 503 | var headers = this._parseHeaders(xhr.getAllResponseHeaders()); 504 | var response = this._transformResponse(xhr.response, headers); 505 | this._onErrorItem(item, response, xhr.status, headers); 506 | this._onCompleteItem(item, response, xhr.status, headers); 507 | }; 508 | 509 | xhr.onabort = () => { 510 | var headers = this._parseHeaders(xhr.getAllResponseHeaders()); 511 | var response = this._transformResponse(xhr.response, headers); 512 | this._onCancelItem(item, response, xhr.status, headers); 513 | this._onCompleteItem(item, response, xhr.status, headers); 514 | }; 515 | 516 | xhr.ontimeout = (e) => { 517 | var headers = this._parseHeaders(xhr.getAllResponseHeaders()); 518 | var response = "Request Timeout."; 519 | this._onTimeoutItem(item); 520 | this._onCompleteItem(item, response, 408, headers); 521 | }; 522 | 523 | xhr.open(item.method, item.url, true); 524 | 525 | xhr.timeout = item.timeout || 0; 526 | xhr.withCredentials = item.withCredentials; 527 | 528 | forEach(item.headers, (value, name) => { 529 | xhr.setRequestHeader(name, value); 530 | }); 531 | 532 | xhr.send(sendable); 533 | } 534 | /** 535 | * The IFrame transport 536 | * @param {FileItem} item 537 | * @private 538 | */ 539 | _iframeTransport(item) { 540 | var form = element(''); 541 | var iframe = element('