├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── components │ └── file-field.js └── uploaders │ ├── s3.js │ └── uploader.js ├── app ├── .gitkeep ├── components │ └── file-field.js └── uploaders │ ├── s3.js │ └── uploader.js ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests ├── .eslintrc ├── .eslintrc.js ├── .jshintrc ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ └── components │ │ │ └── .gitkeep │ ├── config │ │ ├── environment.js │ │ └── targets.js │ ├── mirage │ │ ├── config.js │ │ ├── scenarios │ │ │ └── default.js │ │ └── serializers │ │ │ └── application.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── destroy-app.js │ ├── form-data.js │ └── resolver.js ├── index.html ├── test-helper.js └── unit │ ├── .gitkeep │ ├── file-field-test.js │ ├── s3-test.js │ └── uploader-test.js ├── vendor └── .gitkeep └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | bower_components/** 3 | tests/** 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | server: true, 4 | }, 5 | root: true, 6 | parserOptions: { 7 | ecmaVersion: 2017, 8 | sourceType: 'module' 9 | }, 10 | plugins: [ 11 | 'ember' 12 | ], 13 | extends: [ 14 | 'eslint:recommended', 15 | 'plugin:ember/recommended' 16 | ], 17 | env: { 18 | browser: true 19 | }, 20 | rules: { 21 | 'ember/new-module-imports': 0 22 | }, 23 | overrides: [ 24 | // node files 25 | { 26 | files: [ 27 | 'ember-cli-build.js', 28 | 'index.js', 29 | 'testem.js', 30 | 'blueprints/*/index.js', 31 | 'config/**/*.js', 32 | 'tests/dummy/config/**/*.js' 33 | ], 34 | excludedFiles: [ 35 | 'addon/**', 36 | 'addon-test-support/**', 37 | 'app/**', 38 | 'tests/dummy/app/**' 39 | ], 40 | parserOptions: { 41 | sourceType: 'script', 42 | ecmaVersion: 2015 43 | }, 44 | env: { 45 | browser: false, 46 | node: true 47 | }, 48 | plugins: ['node'], 49 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 50 | // add your custom rules and overrides for node files here 51 | }) 52 | } 53 | ] 54 | }; 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log* 17 | yarn-error.log 18 | testem.log 19 | 20 | # ember-try 21 | .node_modules.ember-try/ 22 | bower.json.ember-try 23 | package.json.ember-try 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /bower_components 2 | /config/ember-try.js 3 | /dist 4 | /tests 5 | /tmp 6 | **/.gitkeep 7 | .bowerrc 8 | .editorconfig 9 | .ember-cli 10 | .eslintrc.js 11 | .gitignore 12 | .watchmanconfig 13 | .travis.yml 14 | bower.json 15 | ember-cli-build.js 16 | testem.js 17 | 18 | # ember-try 19 | .node_modules.ember-try/ 20 | bower.json.ember-try 21 | package.json.ember-try 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | # we recommend testing addons with the same minimum supported node version as Ember CLI 5 | # so that your addon works for all apps 6 | - "4" 7 | 8 | sudo: false 9 | dist: trusty 10 | 11 | addons: 12 | chrome: stable 13 | 14 | cache: 15 | yarn: true 16 | 17 | env: 18 | global: 19 | # See https://git.io/vdao3 for details. 20 | - JOBS=1 21 | matrix: 22 | # we recommend new addons test the current and previous LTS 23 | # as well as latest stable release (bonus points to beta/canary) 24 | - EMBER_TRY_SCENARIO=ember-lts-2.12 25 | - EMBER_TRY_SCENARIO=ember-lts-2.16 26 | - EMBER_TRY_SCENARIO=ember-lts-2.18 27 | - EMBER_TRY_SCENARIO=ember-release 28 | - EMBER_TRY_SCENARIO=ember-beta 29 | - EMBER_TRY_SCENARIO=ember-canary 30 | - EMBER_TRY_SCENARIO=ember-default 31 | 32 | matrix: 33 | fast_finish: true 34 | allow_failures: 35 | - env: EMBER_TRY_SCENARIO=ember-canary 36 | 37 | before_install: 38 | - curl -o- -L https://yarnpkg.com/install.sh | bash 39 | - export PATH=$HOME/.yarn/bin:$PATH 40 | 41 | install: 42 | - yarn install --no-lockfile --non-interactive 43 | 44 | script: 45 | - yarn lint:js 46 | # Usually, it's ok to finish the test scenario without reverting 47 | # to the addon's original dependency state, skipping "cleanup". 48 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup 49 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember Uploader [![Build Status](https://travis-ci.org/benefitcloud/ember-uploader.svg?branch=master)](https://travis-ci.org/benefitcloud/ember-uploader) [![Ember Observer Score](https://emberobserver.com/badges/ember-uploader.svg)](https://emberobserver.com/addons/ember-uploader) 2 | 3 | An Ember.js file uploader that works with any browser that supports 4 | [FormData](http://caniuse.com/#search=FormData). 5 | 6 | ## Getting Started 7 | 8 | Ember Uploader is a Ember CLI compatible addon and can be installed as such. 9 | 10 | ``` 11 | ember install ember-uploader 12 | ``` 13 | 14 | #### Basic Setup 15 | Create a new component called `file-upload` and extend `EmberUploader.FileField` provided by 16 | ember-uploader. If you're using `EmberUploader.FileField`, it will 17 | automatically give you an input field, and will set `files` property when you 18 | choose a file. 19 | 20 | ```js 21 | import FileField from 'ember-uploader/components/file-field'; 22 | import Uploader from 'ember-uploader/uploaders/uploader'; 23 | 24 | export default FileField.extend({ 25 | filesDidChange(files) { 26 | const uploader = Uploader.create({ 27 | url: this.get('url') 28 | }); 29 | 30 | if (!Ember.isEmpty(files)) { 31 | // this second argument is optional and can to be sent as extra data with the upload 32 | uploader.upload(files[0], { whateverObject }); 33 | } 34 | } 35 | }); 36 | ``` 37 | 38 | Call the component, pass it the url, and thats it! 39 | ```hbs 40 | {{file-upload url="/upload"}} 41 | ``` 42 | 43 | #### Ajax Request Method 44 | By default, the request will be sent as `POST`. To override that, set `method` when 45 | creating the object: 46 | 47 | ```js 48 | import Uploader from 'ember-uploader/uploaders/uploader'; 49 | 50 | const uploader = Uploader.create({ 51 | url: '/upload', 52 | method: 'PUT' 53 | }); 54 | ``` 55 | 56 | #### Change Namespace 57 | 58 | ```js 59 | import Uploader from 'ember-uploader/uploaders/uploader'; 60 | 61 | const uploader = Uploader.create({ 62 | paramNamespace: 'post' 63 | }); 64 | 65 | // will be sent as -> post[file]=... 66 | ``` 67 | 68 | #### Change Parameters 69 | By default parameter will be `file` 70 | 71 | ```js 72 | import Uploader from 'ember-uploader/uploaders/uploader'; 73 | 74 | const upload = Uploader.create({ 75 | paramName: 'upload' 76 | }); 77 | 78 | // will be sent as -> upload=... 79 | ``` 80 | 81 | #### Progress 82 | 83 | ```js 84 | uploader.on('progress', e => { 85 | // Handle progress changes 86 | // Use `e.percent` to get percentage 87 | }); 88 | ``` 89 | 90 | #### Finished Uploading 91 | 92 | ```js 93 | uploader.on('didUpload', e => { 94 | // Handle finished upload 95 | }); 96 | ``` 97 | 98 | #### Failed Uploading 99 | 100 | ```js 101 | uploader.on('didError', (jqXHR, textStatus, errorThrown) => { 102 | // Handle unsuccessful upload 103 | }); 104 | ``` 105 | 106 | #### Response 107 | Returned value from uploader will be a promise 108 | 109 | ```js 110 | uploader.upload(file).then(data => { 111 | // Handle success 112 | }, error => { 113 | // Handle failure 114 | }) 115 | ``` 116 | 117 | #### Multiple files 118 | ```js 119 | import FileField from 'ember-uploader/components/file-field'; 120 | import Uploader from 'ember-uploader/uploaders/uploader'; 121 | 122 | export default FileField.extend({ 123 | multiple: true, 124 | url: 'http://example.com/upload', 125 | 126 | filesDidChange(files) { 127 | const uploader = Uploader.create({ 128 | url: this.get('url') 129 | }); 130 | 131 | if (!Ember.isEmpty(files)) { 132 | // this second argument is optional and can to be sent as extra data with the upload 133 | uploader.upload(files, { whatheverObject }); 134 | } 135 | } 136 | }); 137 | ``` 138 | 139 | ### Modifying the request 140 | Ember Uploader uses jQuery.ajax under the hood so it accepts the same 141 | ajax settings via the `ajaxSettings` property which is then merged with any 142 | settings required by Ember Uploader. Here we modify the headers sent with 143 | the request. Note - S3 Uploader uses `signingAjaxSettings` as the relevant key. 144 | 145 | ```js 146 | import Uploader from 'ember-uploader/uploaders/uploader'; 147 | 148 | export default Uploader.extend({ 149 | ajaxSettings: { 150 | headers: { 151 | 'X-Application-Name': 'Uploader Test' 152 | } 153 | } 154 | }); 155 | ``` 156 | 157 | #### Uploading to S3 158 | 159 | Uploading to S3 works in similar manner to the default uploader. There is only 160 | one extra step required before uploading. 161 | 162 | You'll need to setup your backend to be able to sign the upload request, to be 163 | able to make an authenticated request to S3. This step is required to avoid 164 | saving secret token on your client. 165 | 166 | ```js 167 | import FileField from 'ember-uploader/components/file-field'; 168 | import S3Uploader from 'ember-uploader/uploaders/s3'; 169 | 170 | export default FileField.extend({ 171 | signingUrl: '', 172 | 173 | filesDidChange(files) { 174 | const uploader = S3Uploader.create({ 175 | signingUrl: this.get('signingUrl'), 176 | signingAjaxSettings: { 177 | headers: { 178 | 'X-Application-Name': 'Uploader Test' 179 | } 180 | } 181 | }); 182 | 183 | uploader.on('didUpload', response => { 184 | // S3 will return XML with url 185 | let uploadedUrl = $(response).find('Location')[0].textContent; 186 | // http://yourbucket.s3.amazonaws.com/file.png 187 | uploadedUrl = decodeURIComponent(uploadedUrl); 188 | }); 189 | 190 | if (!Ember.isEmpty(files)) { 191 | // Send a sign request then upload to S3 192 | // this second argument is optional and can to be sent as extra data with the upload 193 | uploader.upload(files[0], { whatheverObject }); 194 | } 195 | } 196 | }); 197 | 198 | ``` 199 | 200 | For learning how to setup the backend, check the 201 | [wiki](https://github.com/benefitcloud/ember-uploader/wiki/S3-Server-Setup) 202 | 203 | ## Contributing 204 | In lieu of a formal styleguide, take care to maintain the existing coding 205 | style. Add unit tests for any new or changed functionality. 206 | 207 | Ember Uploader uses [node.js](http://nodejs.org) and 208 | [Ember CLI](https://ember-cli.com/) for builds and tests. You will need to have 209 | these tools installed if you would like to build Ember Uploader. 210 | 211 | ```sh 212 | $ npm install -g ember-cli 213 | ``` 214 | 215 | To get started with development simply do a `yarn install` inside the cloned 216 | repository to install all dependencies needed for running 217 | [Ember CLI](http://www.ember-cli.com/). 218 | 219 | Lint and test your code using: `ember test`. 220 | 221 | ## Thank you 222 | The Ember team, its contributors and community for being awesome. Also thank 223 | you to [Erik Bryn](http://twitter.com/ebryn) and the contributors behind 224 | [ember-model](http://github.com/ebryn/ember-model) as well as 225 | [TJ Holowaychuk](http://twitter.com/tjholowaychuk) for 226 | [component/upload](http://github.com/component/upload). 227 | 228 | ## License 229 | Copyright (c) 2014 Joshua Borton 230 | Licensed under the MIT license. 231 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/addon/.gitkeep -------------------------------------------------------------------------------- /addon/components/file-field.js: -------------------------------------------------------------------------------- 1 | import { isEmpty } from '@ember/utils'; 2 | import Evented from '@ember/object/evented'; 3 | import Component from '@ember/component'; 4 | 5 | export default Component.extend(Evented, { 6 | tagName: 'input', 7 | type: 'file', 8 | attributeBindings: [ 9 | 'name', 10 | 'disabled', 11 | 'form', 12 | 'type', 13 | 'accept', 14 | 'autofocus', 15 | 'required', 16 | 'multiple' 17 | ], 18 | multiple: false, 19 | change (event) { 20 | const input = event.target; 21 | if (!isEmpty(input.files)) { 22 | this.trigger('filesDidChange', input.files); 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /addon/uploaders/s3.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import { Promise } from 'rsvp'; 3 | import { set, get } from '@ember/object'; 4 | import { run } from '@ember/runloop'; 5 | import Uploader from 'ember-uploader/uploaders/uploader'; 6 | import { assign } from '@ember/polyfills'; 7 | 8 | export default Uploader.extend({ 9 | /** 10 | * Target url used to request a signed upload policy 11 | * 12 | * @property url 13 | */ 14 | signingUrl: '/sign', 15 | 16 | /** 17 | * request method for signing 18 | * 19 | * @property method 20 | */ 21 | signingMethod: 'GET', 22 | 23 | /** 24 | * Boolean property changed to true upon signing start and false upon 25 | * signing end 26 | * 27 | * @property isSigning 28 | */ 29 | isSigning: false, 30 | 31 | /** 32 | * Request signed upload policy and upload file(s) and any extra data 33 | * 34 | * @param {object} file A file object 35 | * @param {object} extra Extra data to be sent with the upload 36 | * @return {object} Returns a Ember.RSVP.Promise wrapping the signing 37 | * request object 38 | */ 39 | upload(file, extra = {}) { 40 | return this.sign(file, extra).then((json) => { 41 | let url; 42 | 43 | set(this, 'isUploading', true); 44 | 45 | if (json.endpoint) { 46 | url = json.endpoint; 47 | delete json.endpoint; 48 | } else if (json.region) { 49 | url = `https://s3-${json.region}.amazonaws.com/${json.bucket}`; 50 | delete json.region; 51 | } else { 52 | url = `https://${json.bucket}.s3.amazonaws.com`; 53 | } 54 | 55 | return this.ajax(url, this.createFormData(file, json)); 56 | }); 57 | }, 58 | 59 | /** 60 | * Request signed upload policy 61 | * 62 | * @param {object} file A file object 63 | * @param {object} extra Extra data to be sent with the upload 64 | * @return {object} Returns a Ember.RSVP.Promise wrapping the signing 65 | * request object 66 | */ 67 | sign(file, extra = {}) { 68 | const url = get(this, 'signingUrl'); 69 | const method = get(this, 'signingMethod'); 70 | const signingAjaxSettings = get(this, 'signingAjaxSettings'); 71 | 72 | extra.name = file.name; 73 | extra.type = file.type; 74 | extra.size = file.size; 75 | 76 | const settings = assign( 77 | {}, 78 | { 79 | contentType: 'application/json', 80 | dataType: 'json', 81 | data: method.match(/get/i) ? extra : JSON.stringify(extra), 82 | method, 83 | url 84 | }, 85 | signingAjaxSettings, 86 | ); 87 | 88 | set(this, 'isSigning', true); 89 | 90 | return new Promise((resolve, reject) => { 91 | settings.success = (json) => { 92 | run(null, resolve, this.didSign(json)); 93 | }; 94 | 95 | settings.error = (jqXHR, responseText, errorThrown) => { 96 | run(null, reject, this.didErrorOnSign(jqXHR, responseText, errorThrown)); 97 | }; 98 | 99 | $.ajax(settings); 100 | }); 101 | }, 102 | 103 | /** 104 | * Triggers didErrorOnSign event and sets isSigning to false 105 | * 106 | * @param {object} jqXHR jQuery XMLHttpRequest object 107 | * @param {string} textStatus The status code of the error 108 | * @param {object} errorThrown The error caused 109 | * @return {object} Returns the jQuery XMLHttpRequest 110 | */ 111 | didErrorOnSign(jqXHR, textStatus, errorThrown) { 112 | set(this, 'isSigning', false); 113 | this.trigger('didErrorOnSign'); 114 | this.didError(jqXHR, textStatus, errorThrown); 115 | return jqXHR; 116 | }, 117 | 118 | /** 119 | * Triggers didSign event and returns the signing response 120 | * 121 | * @param {object} response The signing response 122 | * @return {object} The signing response 123 | */ 124 | didSign(response) { 125 | this.trigger('didSign', response); 126 | return response; 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /addon/uploaders/uploader.js: -------------------------------------------------------------------------------- 1 | import { Promise } from 'rsvp'; 2 | import $ from 'jquery'; 3 | import { assign } from '@ember/polyfills'; 4 | import Evented from '@ember/object/evented'; 5 | import EmberObject, { set, get } from '@ember/object'; 6 | import { run } from '@ember/runloop'; 7 | 8 | export default EmberObject.extend(Evented, { 9 | /** 10 | * Target url to upload to 11 | * 12 | * @property url 13 | */ 14 | url: null, 15 | 16 | /** 17 | * ajax request method, by default it will be POST 18 | * 19 | * @property method 20 | */ 21 | method: 'POST', 22 | 23 | /** 24 | * Used to define a namespace for the file param and any extra data params 25 | * that may be sent 26 | * 27 | * @property paramNamespace 28 | */ 29 | paramNamespace: null, 30 | 31 | /** 32 | * The parameter name for the file(s) to be uploaded 33 | * 34 | * @property paramName 35 | */ 36 | paramName: 'file', 37 | 38 | /** 39 | * Boolean property changed to true upon upload start and false upon upload 40 | * end 41 | * 42 | * @property isUploading 43 | */ 44 | isUploading: false, 45 | 46 | /** 47 | * Start upload of file(s) and any extra data 48 | * 49 | * @param {object|array} files One file object or one array of files object 50 | * @param {object} extra Extra data to be sent with the upload 51 | * @return {object} Returns a Ember.RSVP.Promise wrapping the ajax request 52 | * object 53 | */ 54 | upload (files, extra = {}) { 55 | const data = this.createFormData(files, extra); 56 | const url = get(this, 'url'); 57 | const method = get(this, 'method'); 58 | 59 | set(this, 'isUploading', true); 60 | 61 | return this.ajax(url, data, method); 62 | }, 63 | 64 | /** 65 | * Creates the FormData object with the file(s) and any extra data 66 | * 67 | * @param {object|array} files One file object or an array of file objects 68 | * @param {object} extra Extra data to be sent with the upload 69 | * @return {object} Returns a FormData object with the supplied file(s) and 70 | * extra data 71 | */ 72 | createFormData (files, extra = {}) { 73 | const formData = new FormData(); 74 | 75 | for (const prop in extra) { 76 | if (extra.hasOwnProperty(prop)) { 77 | formData.append(this.toNamespacedParam(prop), extra[prop]); 78 | } 79 | } 80 | 81 | // if is a array of files ... 82 | if (files.constructor === FileList || files.constructor === Array) { 83 | const paramKey = `${this.toNamespacedParam(this.paramName)}[]`; 84 | 85 | for (let i = 0; i < files.length; i++) { 86 | // FormData expects the key for arrays to be postfixed with empty 87 | // brackets This same key is used each time a new item is added. 88 | formData.append(paramKey, files[i]); 89 | } 90 | } else { 91 | // if has only one file object ... 92 | formData.append(this.toNamespacedParam(this.paramName), files); 93 | } 94 | 95 | return formData; 96 | }, 97 | 98 | /** 99 | * Returns the param name namespaced if a namespace exists 100 | * 101 | * @param {string} name The param name to namespace 102 | * @return {string} Returns the namespaced param 103 | */ 104 | toNamespacedParam (name) { 105 | return this.paramNamespace ? 106 | `${this.paramNamespace}[${name}]` : 107 | name; 108 | }, 109 | 110 | /** 111 | * Triggers didUpload event with given params and sets isUploading to false 112 | * 113 | * @param {object} data Object of data supplied to the didUpload event 114 | * @return {object} Returns the given data 115 | */ 116 | didUpload (data) { 117 | set(this, 'isUploading', false); 118 | this.trigger('didUpload', data); 119 | return data; 120 | }, 121 | 122 | /** 123 | * Triggers didError event with given params and sets isUploading to false 124 | * 125 | * @param {object} jqXHR jQuery XMLHttpRequest object 126 | * @param {string} textStatus The status code of the error 127 | * @param {object} errorThrown The error caused 128 | * @return {object} Returns the jQuery XMLHttpRequest 129 | */ 130 | didError (jqXHR, textStatus, errorThrown) { 131 | set(this, 'isUploading', false); 132 | 133 | // Borrowed from Ember Data 134 | const isObject = jqXHR !== null && typeof jqXHR === 'object'; 135 | 136 | if (isObject) { 137 | jqXHR.then = null; 138 | if (!jqXHR.errorThrown) { 139 | if (typeof errorThrown === 'string') { 140 | jqXHR.errorThrown = new Error(errorThrown); 141 | } else { 142 | jqXHR.errorThrown = errorThrown; 143 | } 144 | } 145 | } 146 | 147 | this.trigger('didError', jqXHR, textStatus, errorThrown); 148 | 149 | return jqXHR; 150 | }, 151 | 152 | /** 153 | * Triggers progress event supplying event with current percent 154 | * 155 | * @param {object} event Event from xhr onprogress 156 | */ 157 | didProgress (event) { 158 | event.percent = event.loaded / event.total * 100; 159 | this.trigger('progress', event); 160 | }, 161 | 162 | /** 163 | * Triggers isAborting event and sets isUploading to false 164 | */ 165 | abort () { 166 | set(this, 'isUploading', false); 167 | this.trigger('isAborting'); 168 | }, 169 | 170 | /** 171 | * Starts a request to the given url sending the supplied data using the 172 | * supplied request method 173 | * 174 | * @param {string} url The target url for the request 175 | * @param {object} data The data to send with the request 176 | * @param {string} method The request method 177 | * @return {object} Returns a Ember.RSVP.Promise wrapping the ajax request 178 | * object 179 | */ 180 | ajax (url, data = {}, method = this.method) { 181 | const ajaxSettings = assign( 182 | {}, 183 | { 184 | contentType: false, 185 | processData: false, 186 | xhr: () => { 187 | const xhr = $.ajaxSettings.xhr(); 188 | xhr.upload.onprogress = (event) => { 189 | this.didProgress(event); 190 | }; 191 | this.one('isAborting', () => xhr.abort()); 192 | return xhr; 193 | }, 194 | url, 195 | data, 196 | method 197 | }, 198 | get(this, 'ajaxSettings') 199 | ); 200 | 201 | return this.ajaxPromise(ajaxSettings); 202 | }, 203 | 204 | /** 205 | * Starts a request using the supplied settings returning a 206 | * Ember.RSVP.Promise wrapping the ajax request 207 | * 208 | * @param {object} settings The jQuery.ajax compatible settings object 209 | * @return {object} Returns a Ember.RSVP.Promise wrapping the ajax request 210 | */ 211 | ajaxPromise (settings) { 212 | return new Promise((resolve, reject) => { 213 | settings.success = (data) => { 214 | run(null, resolve, this.didUpload(data)); 215 | }; 216 | 217 | settings.error = (jqXHR, responseText, errorThrown) => { 218 | run(null, reject, this.didError(jqXHR, responseText, errorThrown)); 219 | }; 220 | 221 | $.ajax(settings); 222 | }); 223 | } 224 | }); 225 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/app/.gitkeep -------------------------------------------------------------------------------- /app/components/file-field.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-uploader/components/file-field'; -------------------------------------------------------------------------------- /app/uploaders/s3.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-uploader/uploaders/s3'; 2 | -------------------------------------------------------------------------------- /app/uploaders/uploader.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-uploader/uploaders/uploader'; 2 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | 5 | module.exports = function() { 6 | return Promise.all([ 7 | getChannelURL('release'), 8 | getChannelURL('beta'), 9 | getChannelURL('canary') 10 | ]).then((urls) => { 11 | return { 12 | useYarn: true, 13 | scenarios: [ 14 | { 15 | name: 'ember-lts-2.12', 16 | npm: { 17 | devDependencies: { 18 | 'ember-source': '~2.12.0' 19 | } 20 | } 21 | }, 22 | { 23 | name: 'ember-lts-2.16', 24 | npm: { 25 | devDependencies: { 26 | 'ember-source': '~2.16.0' 27 | } 28 | } 29 | }, 30 | { 31 | name: 'ember-lts-2.18', 32 | npm: { 33 | devDependencies: { 34 | 'ember-source': '~2.18.0' 35 | } 36 | } 37 | }, 38 | { 39 | name: 'ember-release', 40 | npm: { 41 | devDependencies: { 42 | 'ember-source': urls[0] 43 | } 44 | } 45 | }, 46 | { 47 | name: 'ember-beta', 48 | npm: { 49 | devDependencies: { 50 | 'ember-source': urls[1] 51 | } 52 | } 53 | }, 54 | { 55 | name: 'ember-canary', 56 | npm: { 57 | devDependencies: { 58 | 'ember-source': urls[2] 59 | } 60 | } 61 | }, 62 | { 63 | name: 'ember-default', 64 | npm: { 65 | devDependencies: {} 66 | } 67 | } 68 | ] 69 | }; 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberAddon(defaults); 7 | 8 | /* 9 | This build file specifies the options for the dummy test app of this 10 | addon, located in `/tests/dummy` 11 | This build file does *not* influence how the addon or the app using it 12 | behave. You most likely want to be modifying `./index.js` or app's build file 13 | */ 14 | 15 | return app.toTree(); 16 | }; 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: 'ember-uploader' 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ember Uploader", 3 | "name": "ember-uploader", 4 | "version": "2.0.0", 5 | "description": "Ember.js addon to facilitate uploading", 6 | "homepage": "https://github.com/benefitcloud/ember-uploader", 7 | "license": "MIT", 8 | "author": "Joshua Borton ", 9 | "repository": "git://github.com/benefitcloud/ember-uploader.git", 10 | "directories": { 11 | "doc": "doc", 12 | "test": "tests" 13 | }, 14 | "scripts": { 15 | "lint:js": "eslint ./*.js addon addon-test-support app config lib server test-support tests", 16 | "build": "ember build", 17 | "start": "ember serve", 18 | "test": "ember try:each" 19 | }, 20 | "engines": { 21 | "node": "^4.5 || 6.* || >= 7.*" 22 | }, 23 | "dependencies": { 24 | "ember-cli-babel": "^6.6.0" 25 | }, 26 | "devDependencies": { 27 | "broccoli-asset-rev": "^2.4.5", 28 | "ember-cli": "~3.1.4", 29 | "ember-cli-app-version": "^3.2.0", 30 | "ember-cli-dependency-checker": "^2.0.0", 31 | "ember-cli-eslint": "^4.2.1", 32 | "ember-cli-htmlbars": "^2.0.1", 33 | "ember-cli-htmlbars-inline-precompile": "^1.0.0", 34 | "ember-cli-inject-live-reload": "^1.4.1", 35 | "ember-cli-mirage": "^0.4.7", 36 | "ember-cli-qunit": "^4.1.1", 37 | "ember-cli-shims": "^1.2.0", 38 | "ember-cli-sri": "^2.1.0", 39 | "ember-cli-uglify": "^2.0.0", 40 | "ember-disable-prototype-extensions": "^1.1.2", 41 | "ember-export-application-global": "^2.0.0", 42 | "ember-load-initializers": "^1.0.0", 43 | "ember-maybe-import-regenerator": "^0.1.6", 44 | "ember-resolver": "^4.0.0", 45 | "ember-sinon": "^2.1.0", 46 | "ember-sinon-qunit": "3.1.0", 47 | "ember-source": "~3.1.2", 48 | "ember-source-channel-url": "^1.1.0", 49 | "ember-try": "^0.2.23", 50 | "eslint-plugin-ember": "^5.2.0", 51 | "eslint-plugin-node": "^6.0.1", 52 | "loader.js": "^4.2.3" 53 | }, 54 | "keywords": [ 55 | "ember-addon", 56 | "uploading", 57 | "uploader" 58 | ], 59 | "ember-addon": { 60 | "configPath": "tests/dummy/config", 61 | "versionCompatibility": { 62 | "ember": ">=1.13" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | mode: 'ci', 13 | args: [ 14 | // --no-sandbox is needed when running Chrome inside a container 15 | process.env.TRAVIS ? '--no-sandbox' : null, 16 | 17 | '--disable-gpu', 18 | '--headless', 19 | '--remote-debugging-port=0', 20 | '--window-size=1440,900' 21 | ].filter(Boolean) 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc", 3 | "env" : { 4 | "mocha" : true 5 | }, 6 | "globals" : { 7 | "deepEquals" : false, 8 | "expect" : false, 9 | "sinon" : false 10 | }, 11 | "rules": { 12 | "func-names" : 0, 13 | "no-unused-expressions" : 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | embertest: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "server", 4 | "document", 5 | "window", 6 | "location", 7 | "setTimeout", 8 | "$", 9 | "-Promise", 10 | "define", 11 | "console", 12 | "visit", 13 | "exists", 14 | "fillIn", 15 | "click", 16 | "keyEvent", 17 | "triggerEvent", 18 | "find", 19 | "findWithAssert", 20 | "wait", 21 | "DS", 22 | "andThen", 23 | "currentURL", 24 | "currentPath", 25 | "currentRouteName" 26 | ], 27 | "node": false, 28 | "browser": false, 29 | "boss": true, 30 | "curly": true, 31 | "debug": false, 32 | "devel": false, 33 | "eqeqeq": true, 34 | "evil": true, 35 | "forin": false, 36 | "immed": false, 37 | "laxbreak": false, 38 | "newcap": true, 39 | "noarg": true, 40 | "noempty": false, 41 | "nonew": false, 42 | "nomen": false, 43 | "onevar": false, 44 | "plusplus": false, 45 | "regexp": false, 46 | "undef": true, 47 | "sub": true, 48 | "strict": false, 49 | "white": false, 50 | "eqnull": true, 51 | "esnext": true, 52 | "unused": true 53 | } 54 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | }); 11 | 12 | export default Router; 13 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{!-- The following component displays Ember's default welcome message. --}} 2 | Ember Uploader 3 | {{!-- Feel free to remove this! --}} 4 | 5 | {{outlet}} 6 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /tests/dummy/mirage/config.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | this.get('/sign', { bucket: 'testbucket' }); 3 | this.post('https://testbucket.s3.amazonaws.com', { message: 'OK'}, 201); 4 | this.post('/upload', { message: 'OK' }, 201); 5 | this.post('/invalid', { message: 'Not Found' }, 404); 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/mirage/scenarios/default.js: -------------------------------------------------------------------------------- 1 | export default function(/* server */) { 2 | 3 | /* 4 | Seed your development database using your factories. 5 | This data will not be loaded in your tests. 6 | */ 7 | 8 | // server.createList('post', 10); 9 | } 10 | -------------------------------------------------------------------------------- /tests/dummy/mirage/serializers/application.js: -------------------------------------------------------------------------------- 1 | import { JSONAPISerializer } from 'ember-cli-mirage'; 2 | 3 | export default JSONAPISerializer.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | 3 | export default function destroyApp(application) { 4 | run(application, 'destroy'); 5 | if (window.server) { 6 | window.server.shutdown(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/helpers/form-data.js: -------------------------------------------------------------------------------- 1 | import { isArray } from '@ember/array'; 2 | function TestableFormData() { 3 | this.data = {} 4 | } 5 | 6 | TestableFormData.inject = function() { 7 | if (window) { 8 | this.OldFormData = window.FormData; 9 | window.FormData = TestableFormData; 10 | } 11 | } 12 | 13 | TestableFormData.remove = function() { 14 | if (window && this.OldFormData) { 15 | window.FormData = this.OldFormData; 16 | delete this.OldFormData; 17 | } 18 | } 19 | 20 | TestableFormData.prototype.append = function(key, value) { 21 | // FormData expects the key for arrays to be postfixed with empty brackets 22 | // This same key is used each time a new item is added. 23 | let matches = key.match(/^(.*)\[\]$/); 24 | 25 | if (matches) { 26 | const arrayKey = matches.reverse()[0]; 27 | 28 | if (!isArray(this.data[arrayKey])) { 29 | this.data[arrayKey] = []; 30 | } 31 | 32 | this.data[arrayKey].push(value); 33 | } else { 34 | this.data[key] = value; 35 | } 36 | } 37 | 38 | export default TestableFormData; 39 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from '../../resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/file-field-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import FileField from 'dummy/components/file-field'; 4 | 5 | module('EmberUploader.FileField', function(hooks) { 6 | setupTest(hooks); 7 | 8 | test('it triggers `filesDidChange` on change', function(assert) { 9 | let result; 10 | assert.expect(1); 11 | 12 | const fileField = this.owner.factoryFor('component:file-field').create({ 13 | filesDidChange (files) { 14 | result = files; 15 | } 16 | }); 17 | 18 | fileField.change({ target: { files: [ 'foo' ] }}); 19 | 20 | assert.deepEqual(result, ['foo'], 'it returns the files that changed'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/unit/s3-test.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import { computed } from '@ember/object'; 4 | import $ from 'jquery'; 5 | import S3Uploader from 'ember-uploader/uploaders/s3'; 6 | import test from 'ember-sinon-qunit/test-support/test'; 7 | import { startMirage } from 'dummy/initializers/ember-cli-mirage'; 8 | 9 | let file; 10 | 11 | module('EmberUploader.S3Uploader', function(hooks) { 12 | setupTest(hooks); 13 | 14 | hooks.beforeEach(function() { 15 | this.server = startMirage(); 16 | 17 | if (typeof WebKitBlobBuilder === 'undefined') { 18 | file = new Blob(['test'], { type: 'text/plain' }); 19 | } else { 20 | const builder = new WebKitBlobBuilder(); 21 | builder.append('test'); 22 | file = builder.getBlob(); 23 | } 24 | 25 | file.mime = 'text/plain'; 26 | file.name = 'test.txt'; 27 | }); 28 | 29 | hooks.afterEach(function() { 30 | this.server.shutdown(); 31 | }); 32 | 33 | test('it has a sign url of "/api/signed-url"', function(assert) { 34 | assert.expect(1); 35 | 36 | let uploader = S3Uploader.create({ 37 | signingUrl: '/api/signed-url' 38 | }); 39 | 40 | assert.equal(uploader.signingUrl, '/api/signed-url'); 41 | }); 42 | 43 | test('it uploads after signing', function(assert) { 44 | assert.expect(1); 45 | 46 | let uploader = S3Uploader.extend({ 47 | ajax() { 48 | assert.ok(true, 'ajax method was called'); 49 | } 50 | }).create(); 51 | 52 | uploader.upload(file); 53 | }); 54 | 55 | test('it has default sign request type as "GET"', function(assert) { 56 | assert.expect(1); 57 | 58 | let uploader = S3Uploader.create(); 59 | assert.equal(uploader.get('signingMethod'), 'GET'); 60 | }); 61 | 62 | test('sign request type can be customized', function(assert) { 63 | assert.expect(1); 64 | 65 | let uploader = S3Uploader.create({ 66 | method: 'POST' 67 | }); 68 | assert.equal(uploader.get('method'), 'POST'); 69 | }); 70 | 71 | test('uploads to s3', async function(assert) { 72 | assert.expect(1); 73 | 74 | let uploader = S3Uploader.create({ 75 | file: file 76 | }); 77 | 78 | uploader.on('didSign', function(data) { 79 | assert.ok(true, 'didUpload callback was called'); 80 | }); 81 | 82 | await uploader.upload(file); 83 | }); 84 | 85 | test('it allows overriding ajax sign settings', function(assert) { 86 | this.stub($, 'ajax'); 87 | 88 | assert.expect(1); 89 | 90 | const settings = { 91 | headers: { 92 | 'Content-Type': 'text/html' 93 | } 94 | }; 95 | 96 | const uploader = S3Uploader.extend({ 97 | signingAjaxSettings: settings 98 | }).create(); 99 | 100 | uploader.sign('/test'); 101 | 102 | assert.equal($.ajax.getCall(0).args[0].headers['Content-Type'], 'text/html'); 103 | }); 104 | 105 | test('it allows signingAjaxSettings to be a computed property', function(assert) { 106 | this.stub($, 'ajax'); 107 | 108 | assert.expect(2); 109 | 110 | const uploader = S3Uploader.extend({ 111 | _testIterator: 0, 112 | 113 | signingAjaxSettings: computed('_testIterator', function() { 114 | return { 115 | headers: { 116 | 'X-My-Incrementor': this.get('_testIterator'), 117 | } 118 | }; 119 | }), 120 | }).create(); 121 | 122 | uploader.sign('/test'); 123 | assert.equal($.ajax.getCall(0).args[0].headers['X-My-Incrementor'], '0'); 124 | 125 | uploader.set('_testIterator', 1); 126 | uploader.sign('/test'); 127 | assert.equal($.ajax.getCall(1).args[0].headers['X-My-Incrementor'], '1'); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /tests/unit/uploader-test.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import { computed } from '@ember/object'; 4 | import $ from 'jquery'; 5 | import Uploader from 'ember-uploader/uploaders/uploader'; 6 | import test from 'ember-sinon-qunit/test-support/test'; 7 | import TestableFormData from '../helpers/form-data'; 8 | import { startMirage } from 'dummy/initializers/ember-cli-mirage'; 9 | 10 | let file; 11 | 12 | module('EmberUploader.Uploader', function(hooks) { 13 | setupTest(hooks); 14 | 15 | hooks.beforeEach(function() { 16 | this.server = startMirage(); 17 | 18 | if (typeof WebKitBlobBuilder === "undefined") { 19 | file = new Blob(['test'], { type: 'text/plain' }); 20 | } else { 21 | const builder = new WebKitBlobBuilder(); 22 | builder.append('test'); 23 | file = builder.getBlob(); 24 | } 25 | 26 | TestableFormData.inject(); 27 | }); 28 | 29 | hooks.afterEach(function() { 30 | this.server.shutdown(); 31 | TestableFormData.remove(); 32 | }); 33 | 34 | test("has a url of '/test'", function(assert) { 35 | let uploader = Uploader.extend({ 36 | url: '/test' 37 | }).create(); 38 | 39 | assert.equal(uploader.get('url'), '/test'); 40 | }); 41 | 42 | test("has a paramName of 'upload'", function(assert) { 43 | let uploader = Uploader.extend({ 44 | paramName: 'upload' 45 | }).create(); 46 | 47 | assert.equal(uploader.get('paramName'), 'upload'); 48 | }); 49 | 50 | test("has a paramNamespace of 'post'", function(assert) { 51 | let uploader = Uploader.extend({ 52 | paramNamespace: 'post' 53 | }).create(); 54 | 55 | assert.equal(uploader.get('paramNamespace'), 'post'); 56 | }); 57 | 58 | test("creates a param namespace", function(assert) { 59 | let uploader = Uploader.extend({ 60 | paramNamespace: 'post' 61 | }).create(); 62 | 63 | assert.equal(uploader.toNamespacedParam('upload'), 'post[upload]'); 64 | }); 65 | 66 | test("has an ajax request of type 'PUT'", function(assert) { 67 | let uploader = Uploader.extend({ 68 | method: 'PUT' 69 | }).create(); 70 | 71 | assert.equal(uploader.get('method'), 'PUT'); 72 | }); 73 | 74 | test("it can upload multiple files", function(assert) { 75 | assert.expect(3); 76 | 77 | let uploader = Uploader.extend({ 78 | paramName: 'files' 79 | }).create(); 80 | 81 | let formData = uploader.createFormData([1,2,3]); 82 | assert.equal(formData.data['files'][0], 1); 83 | assert.equal(formData.data['files'][1], 2); 84 | assert.equal(formData.data['files'][2], 3); 85 | }); 86 | 87 | test("uploads to the given url", function(assert) { 88 | assert.expect(1); 89 | 90 | let uploader = Uploader.extend({ 91 | url: '/upload', 92 | file: file 93 | }).create(); 94 | 95 | uploader.on('didUpload', function(data) { 96 | assert.ok(true); 97 | }); 98 | 99 | uploader.upload(file); 100 | }); 101 | 102 | test("uploads promise gets resolved", function(assert) { 103 | assert.expect(1); 104 | 105 | let uploader = Uploader.extend({ 106 | url: '/upload', 107 | file: file 108 | }).create(); 109 | 110 | uploader.upload(file).then(function(data) { 111 | assert.ok(true); 112 | }); 113 | }); 114 | 115 | test("uploads promise gets rejected", function(assert) { 116 | assert.expect(1); 117 | 118 | let uploader = Uploader.extend({ 119 | url: '/invalid', 120 | file: file 121 | }).create(); 122 | 123 | uploader.upload(file).then(function(data) { 124 | }, function(data) { 125 | assert.ok(true); 126 | }); 127 | }); 128 | 129 | test("error response not undefined", function(assert) { 130 | assert.expect(1); 131 | 132 | let uploader = Uploader.extend({ 133 | url: '/invalid' 134 | }).create(); 135 | 136 | uploader.upload(file).then(null, function(error) { 137 | assert.equal(error.status, 404); 138 | }); 139 | }); 140 | 141 | test("emits progress event", async function(assert) { 142 | assert.expect(1); 143 | 144 | server.timing = 100; 145 | 146 | let uploader = Uploader.extend({ 147 | url: '/upload', 148 | file: file 149 | }).create(); 150 | 151 | uploader.on('progress', function(e) { 152 | assert.ok(true, 'progress event was emitted'); 153 | }); 154 | 155 | await uploader.upload(file); 156 | }); 157 | 158 | test("it can receive extra data", function(assert) { 159 | assert.expect(1); 160 | 161 | let data = { test: 'valid' }; 162 | 163 | let uploader = Uploader.extend({ 164 | url: '/upload', 165 | createFormData: function(file, extra) { 166 | assert.equal(extra, data); 167 | return this._super(file, extra); 168 | } 169 | }).create(); 170 | 171 | uploader.upload(file, data); 172 | }); 173 | 174 | test("it allows overriding ajax settings", function(assert) { 175 | this.stub($, 'ajax'); 176 | 177 | assert.expect(1); 178 | 179 | let uploader = Uploader.extend({ 180 | ajaxSettings: { 181 | headers: { 182 | 'Content-Type': 'text/html' 183 | } 184 | } 185 | }).create(); 186 | 187 | uploader.upload(file); 188 | 189 | assert.equal($.ajax.getCall(0).args[0].headers['Content-Type'], 'text/html'); 190 | }); 191 | 192 | test("it allows ajaxSettings to be a computed property", function(assert) { 193 | this.stub($, 'ajax'); 194 | 195 | assert.expect(2); 196 | 197 | let uploader = Uploader.extend({ 198 | _testIterator: 0, 199 | 200 | ajaxSettings: computed('_testIterator', function() { 201 | return { 202 | headers: { 203 | 'X-My-Incrementor': this.get('_testIterator'), 204 | } 205 | }; 206 | }), 207 | }).create(); 208 | 209 | uploader.upload(file); 210 | assert.equal($.ajax.getCall(0).args[0].headers['X-My-Incrementor'], '0'); 211 | 212 | uploader.set('_testIterator', 1); 213 | uploader.upload(file); 214 | assert.equal($.ajax.getCall(1).args[0].headers['X-My-Incrementor'], '1'); 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benefitcloud/ember-uploader/dd3f31a11e2e8cf3c3c51f71a7ddb9a7b10d7a60/vendor/.gitkeep --------------------------------------------------------------------------------