├── tests ├── unit │ └── .gitkeep ├── dummy │ ├── app │ │ ├── helpers │ │ │ ├── .gitkeep │ │ │ └── file-icon.js │ │ ├── models │ │ │ └── .gitkeep │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ └── application.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ └── application.js │ │ ├── templates │ │ │ ├── components │ │ │ │ └── .gitkeep │ │ │ └── application.hbs │ │ ├── router.js │ │ ├── app.js │ │ ├── index.html │ │ └── styles │ │ │ └── app.css │ ├── public │ │ ├── robots.txt │ │ └── crossdomain.xml │ └── config │ │ ├── targets.js │ │ └── environment.js ├── .eslintrc.js ├── test-helper.js ├── helpers │ ├── destroy-app.js │ ├── resolver.js │ ├── start-app.js │ └── module-for-acceptance.js ├── index.html └── acceptance │ └── x-file-input-test.js ├── .watchmanconfig ├── app └── components │ └── x-file-input.js ├── .template-lintrc.js ├── config ├── environment.js └── ember-try.js ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── addon ├── test-helpers │ ├── select-file-async.js │ └── select-file-unit.js ├── templates │ └── components │ │ └── x-file-input.hbs └── components │ └── x-file-input.js ├── .ember-cli ├── .eslintrc.js ├── .npmignore ├── index.js ├── .editorconfig ├── .gitignore ├── testem.js ├── .eslintignore ├── ember-cli-build.js ├── vendor └── style.css ├── LICENSE.md ├── package.json ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md ├── README.md └── .lint-todo /tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | embertest: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /app/components/x-file-input.js: -------------------------------------------------------------------------------- 1 | export { default } from 'emberx-file-input/components/x-file-input'; 2 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | rules: { 6 | quotes: false, 7 | }, 8 | }; -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { setResolver } from 'ember-mocha'; 3 | 4 | setResolver(resolver); 5 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function(/* environment, appConfig */) { 5 | return { }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import { run } from '@ember/runloop'; 2 | 3 | export default function destroyApp(application) { 4 | run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "02:00" 8 | open-pull-requests-limit: 10 -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | browsers: [ 4 | 'ie 9', 5 | 'last 1 Chrome versions', 6 | 'last 1 Firefox versions', 7 | 'last 1 Safari versions' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /addon/test-helpers/select-file-async.js: -------------------------------------------------------------------------------- 1 | import { registerAsyncHelper } from '@ember/test'; 2 | 3 | export default registerAsyncHelper('selectFile', function(app, selector, file) { 4 | return triggerEvent( 5 | selector, 6 | 'change', 7 | { testingFiles: [file] } 8 | ); 9 | }); 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-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 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2017, 5 | sourceType: 'module' 6 | }, 7 | extends: 'eslint:recommended', 8 | env: { 9 | browser: true 10 | }, 11 | rules: { 12 | }, 13 | globals: { 14 | '$': false, 15 | 'triggerEvent': false 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /addon/templates/components/x-file-input.hbs: -------------------------------------------------------------------------------- 1 | 3 | 10 | -------------------------------------------------------------------------------- /.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 | .gitignore 11 | .eslintrc.js 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 | -------------------------------------------------------------------------------- /addon/test-helpers/select-file-unit.js: -------------------------------------------------------------------------------- 1 | function buildChangeEvent(options = {}) { 2 | let event = document.createEvent('Events'); 3 | event.initEvent('change', true, true); 4 | $.extend(event, options); 5 | return event; 6 | } 7 | 8 | export default function(selector, file) { 9 | let changeEvent = buildChangeEvent({ testingFiles: [file] }); 10 | $(selector)[0].dispatchEvent(changeEvent); 11 | } 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'emberx-file-input', 6 | included: function(app) { 7 | this._super.included(app); 8 | 9 | var appImport; 10 | if (this && typeof this.import === 'function') { 11 | appImport = this.import.bind(this); 12 | } else { 13 | appImport = app.import.bind(app); 14 | } 15 | 16 | appImport('vendor/style.css'); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-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; -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | .eslintcache -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | test_page: 'tests/index.html?hidepassed', 4 | disable_watching: true, 5 | launch_in_ci: [ 6 | 'Chrome' 7 | ], 8 | launch_in_dev: [ 9 | 'Chrome' 10 | ], 11 | browser_args: { 12 | Chrome: { 13 | mode: 'ci', 14 | args: [ 15 | '--disable-gpu', 16 | '--headless', 17 | '--remote-debugging-port=0', 18 | '--window-size=1440,900' 19 | ] 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | .eslintrc.js 19 | .template-lintrc.js 20 | 21 | # ember-try 22 | /.node_modules.ember-try/ 23 | /bower.json.ember-try 24 | /npm-shrinkwrap.json.ember-try 25 | /package.json.ember-try 26 | /package-lock.json.ember-try 27 | /yarn.lock.ember-try 28 | /config/ember-try.js 29 | 30 | # tests 31 | /tests/.eslintrc.js -------------------------------------------------------------------------------- /tests/dummy/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { htmlSafe } from '@ember/string'; 2 | import { computed } from '@ember/object'; 3 | import Controller from '@ember/controller'; 4 | 5 | export default Controller.extend({ 6 | multiFiles: [], 7 | photoName: null, 8 | photoPreviewUrl: null, 9 | photoPreview: computed('photoPreviewUrl', function(){ 10 | let url = this.get('photoPreviewUrl'); 11 | 12 | return htmlSafe(`background-image: url("${url}")`); 13 | }), 14 | 15 | actions: { 16 | doSomething(files, reset) { 17 | reset(); 18 | } 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Application from '../../app'; 2 | import config from '../../config/environment'; 3 | import { merge } from '@ember/polyfills'; 4 | import { run } from '@ember/runloop'; 5 | 6 | export default function startApp(attrs) { 7 | let attributes = merge({}, config.APP); 8 | attributes = merge(attributes, attrs); // use defaults, but you can override; 9 | 10 | return run(() => { 11 | let application = Application.create(attributes); 12 | application.setupForTesting(); 13 | application.injectTestHelpers(); 14 | return application; 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/file-icon.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { htmlSafe } from '@ember/string'; 3 | 4 | export function fileIcon(params) { 5 | let iconClass = "fa fa-file-o"; 6 | 7 | if(new RegExp("image/*").test(params)) { 8 | iconClass = "fa fa-file-image-o"; 9 | } else if(new RegExp("text/*").test(params)) { 10 | iconClass = "fa fa-file-text-o"; 11 | } else if(new RegExp("application/pdf").test(params)) { 12 | iconClass = "fa fa-file-pdf-o"; 13 | } 14 | 15 | return new htmlSafe(``); 16 | } 17 | 18 | export default helper(fileIcon); 19 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 5 | 6 | module.exports = function(defaults) { 7 | let app = new EmberAddon(defaults, { 8 | // Add options here 9 | }); 10 | 11 | app.import('vendor/style.css'); 12 | /* 13 | This build file specifies the options for the dummy test app of this 14 | addon, located in `/tests/dummy` 15 | This build file does *not* influence how the addon or the app using it 16 | behave. You most likely want to be modifying `./index.js` or app's build file 17 | */ 18 | 19 | return app.toTree(); 20 | }; 21 | -------------------------------------------------------------------------------- /vendor/style.css: -------------------------------------------------------------------------------- 1 | /* Hide the original input */ 2 | .x-file--input { 3 | width: 1px; 4 | height: 0; 5 | max-width: 0; 6 | opacity: 0; 7 | overflow: hidden; 8 | z-index: -1; 9 | } 10 | 11 | .x-file--input + label { 12 | display: inline-block; 13 | cursor: pointer; 14 | } 15 | 16 | .x-file--input:focus + label, 17 | .x-file--input + label:hover { 18 | /* Overwrite this in your own css file with styles for :focus & :hover */ 19 | } 20 | 21 | .x-file--input:focus + label { 22 | outline: 1px dotted #000; 23 | outline: -webkit-focus-ring-color auto 5px; 24 | } 25 | 26 | .x-file--input + label * { 27 | pointer-events: none; 28 | } 29 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import { resolve } from 'rsvp'; 3 | import startApp from '../helpers/start-app'; 4 | import destroyApp from '../helpers/destroy-app'; 5 | 6 | export default function(name, options = {}) { 7 | module(name, { 8 | beforeEach() { 9 | this.application = startApp(); 10 | 11 | if (options.beforeEach) { 12 | return options.beforeEach.apply(this, arguments); 13 | } 14 | }, 15 | 16 | afterEach() { 17 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments); 18 | return resolve(afterEach).then(() => destroyApp(this.application)); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /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/routes/application.js: -------------------------------------------------------------------------------- 1 | /* global FileReader, alert */ 2 | import Route from '@ember/routing/route'; 3 | 4 | export default Route.extend({ 5 | 6 | actions: { 7 | uploadAPhoto(fileList) { 8 | let re = new RegExp('image/*'); 9 | 10 | if(re.test(fileList[0].type)) { 11 | let controller = this.controllerFor('application'); 12 | controller.set('photoName', fileList[0].name); 13 | 14 | let reader = new FileReader(); 15 | 16 | reader.onloadend = function() { 17 | controller.set('photoPreviewUrl', reader.result); 18 | }; 19 | 20 | reader.readAsDataURL(fileList[0]); 21 | } else { 22 | alert(`File must be an image. You tried to upload: ${fileList[0].type}`); 23 | } 24 | }, 25 | 26 | uploadManyFiles(fileList) { 27 | let controller = this.controllerFor('application'); 28 | 29 | controller.set('multiFiles', fileList); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 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 | -------------------------------------------------------------------------------- /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/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | .custom-class label { 2 | background: #34495e; 3 | padding: 10px; 4 | color: white; 5 | border-radius: 5px; 6 | } 7 | 8 | .x-file-input { 9 | text-align: center; 10 | } 11 | 12 | .x-file--input:focus + label, 13 | .custom-class label:hover { 14 | background-color: #2C3E50; 15 | } 16 | 17 | .demo-wrapper { 18 | width: 80%; 19 | max-width: 600px; 20 | text-align: center; 21 | margin: auto; 22 | margin-top: 60px; 23 | background-color: #f3f3f3; 24 | padding: 30px; 25 | font-family: helvetica; 26 | border-radius: 3px; 27 | } 28 | 29 | .demo-camera-icon { 30 | font-size: 40px; 31 | color: #34495e; 32 | padding: 10px; 33 | text-align: center; 34 | } 35 | 36 | .demo-photo-preview { 37 | width: 200px; 38 | height: 200px; 39 | background-size: cover; 40 | background-repeat: no-repeat; 41 | margin: auto; 42 | border-radius: 100%; 43 | border: 1px solid #d3d3d3; 44 | } 45 | 46 | .photo-name { 47 | padding: 8px; 48 | border: 1px solid #d3d3d3; 49 | border-radius: 5px; 50 | color: #d3d3d3; 51 | margin-top: 12px; 52 | } 53 | 54 | .file-name { 55 | padding: 8px; 56 | border: 1px solid #34495e; 57 | border-radius: 5px; 58 | color: #34495e; 59 | margin-top: 12px; 60 | } 61 | 62 | .photo-name label:hover { 63 | border: 1px solid #34495e; 64 | color: #34495e; 65 | } 66 | 67 | .fa { 68 | font-size: 50px; 69 | } 70 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Basic Emberx-File-Input

3 | 4 |
5 | {{#x-file-input class="custom-class" action="uploadAPhoto" id="spec-file-input"}} 6 |

Shall you upload?

7 | {{/x-file-input}} 8 |
9 | 10 |
11 | Non block: 12 | {{x-file-input class="custom-class" 13 | action="uploadAPhoto" id="spec-file-input-blockless"}} 14 |
15 |
16 | 17 |
18 | {{#x-file-input action="uploadAPhoto" id="spec-file-input-two"}} 19 | {{#if photoName}} 20 |
21 |
{{photoName}}
22 | {{else}} 23 |
24 | {{/if}} 25 | {{/x-file-input}} 26 |
27 | 28 |
29 | {{x-file-input action="uploadManyFiles" class="custom-class" 30 | id="spec-file-input-three" multiple=true alt="Upload multiple"}} 31 | 32 | {{#each multiFiles as |file|}} 33 |

34 | {{file-icon file.type}} 35 | {{file.name}} 36 |

37 | {{/each}} 38 |
39 | 40 |
41 | {{x-file-input alt="Upload Reset" action="doSomething" 42 | id="spec-file-input-reset" class="custom-class"}} 43 |
44 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function(environment) { 5 | let ENV = { 6 | modulePrefix: 'dummy', 7 | environment, 8 | rootURL: '/', 9 | locationType: 'auto', 10 | EmberENV: { 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. 'with-controller': true 14 | }, 15 | EXTEND_PROTOTYPES: { 16 | // Prevent Ember Data from overriding Date.parse. 17 | Date: false 18 | } 19 | }, 20 | 21 | APP: { 22 | // Here you can pass flags/options to your application instance 23 | // when it is created 24 | } 25 | }; 26 | 27 | if (environment === 'development') { 28 | // ENV.APP.LOG_RESOLVER = true; 29 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 30 | // ENV.APP.LOG_TRANSITIONS = true; 31 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 32 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 33 | } 34 | 35 | if (environment === 'test') { 36 | // Testem prefers this... 37 | ENV.locationType = 'none'; 38 | 39 | // keep test console output quieter 40 | ENV.APP.LOG_ACTIVE_GENERATION = false; 41 | ENV.APP.LOG_VIEW_LOOKUPS = false; 42 | 43 | ENV.APP.rootElement = '#ember-testing'; 44 | } 45 | 46 | if (environment === 'production') { 47 | ENV.locationType = 'hash'; 48 | ENV.rootURL = '/emberx-file-input/'; 49 | 50 | } 51 | 52 | return ENV; 53 | }; 54 | -------------------------------------------------------------------------------- /addon/components/x-file-input.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | import Component from '@ember/component'; 3 | import layout from '../templates/components/x-file-input'; 4 | 5 | export default Component.extend({ 6 | classNameBindings: [':x-file-input', 'disabled:x-file-input--disabled'], 7 | attributeBindings: ['accept'], 8 | tagName: 'span', 9 | layout: layout, 10 | tabindex: 0, 11 | 12 | /** 13 | * The text displayed when no block is passed. 14 | * 15 | * @property alt 16 | * @default "Upload" 17 | */ 18 | alt: "Upload", 19 | 20 | /** 21 | * Listens for change events on the native file input and dispatches 22 | * the corresponding action up the context chain. 23 | * 24 | * @private 25 | * @method 26 | * @param {$.Event} e Native change event 27 | */ 28 | change(e) { 29 | this.sendAction("action", this.files(e), this.resetInput.bind(this)); 30 | }, 31 | 32 | /** 33 | * Resets the value of the input so you can select the same file 34 | * multiple times. 35 | * 36 | * @method 37 | */ 38 | resetInput() { 39 | this.$('.x-file--input').val(''); 40 | }, 41 | 42 | /** 43 | * Generates a random ID to relate the label to the input. 44 | * 45 | * @method 46 | * @private 47 | */ 48 | randomId: computed(function() { 49 | return Math.random().toString(36).substring(7); 50 | }), 51 | 52 | /** 53 | * Gets files from event object. 54 | * 55 | * @method 56 | * @private 57 | * @param {$.Event || Event} 58 | */ 59 | files(e) { 60 | if (e.target.files) { 61 | return e.target.files; 62 | } else if (e.testingFiles) { 63 | return e.testingFiles; 64 | } else { 65 | // testingFiles will not exist on e 66 | // when it is a JQuery.Event 67 | return e.originalEvent.testingFiles; 68 | } 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emberx-file-input", 3 | "version": "1.2.1", 4 | "description": "A tiny Ember component which does one thing and only: select files beautifully.", 5 | "keywords": [ 6 | "ember-addon", 7 | "file input", 8 | "file selector", 9 | "custom file input" 10 | ], 11 | "license": "MIT", 12 | "author": "", 13 | "directories": { 14 | "doc": "doc", 15 | "test": "tests" 16 | }, 17 | "repository": "https://github.com/adopted-ember-addons/emberx-file-input", 18 | "scripts": { 19 | "build": "ember build", 20 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 21 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", 22 | "lint:hbs": "ember-template-lint .", 23 | "lint:hbs:fix": "ember-template-lint . --fix", 24 | "lint:js": "eslint . --cache", 25 | "lint:js:fix": "eslint . --fix", 26 | "start": "ember serve", 27 | "test": "npm-run-all lint test:*", 28 | "test:ember": "ember test", 29 | "test:ember-compatibility": "ember try:each" 30 | }, 31 | "dependencies": { 32 | "chai-jquery": "^2.0.0", 33 | "ember-cli-babel": "^7.26.11", 34 | "ember-cli-htmlbars": "^6.0.1" 35 | }, 36 | "devDependencies": { 37 | "@embroider/test-setup": "^1.0.0", 38 | "broccoli-asset-rev": "^3.0.0", 39 | "ember-ajax": "^3.0.0", 40 | "ember-auto-import": "^2.4.1", 41 | "ember-cli": "~3.18.0", 42 | "ember-cli-chai": "^0.4.0", 43 | "ember-cli-dependency-checker": "^3.3.1", 44 | "ember-cli-eslint": "^5.1.0", 45 | "ember-cli-github-pages": "^0.1.2", 46 | "ember-cli-inject-live-reload": "^1.4.1", 47 | "ember-cli-mocha": "^0.15.0", 48 | "ember-cli-release": "^1.0.0-beta.2", 49 | "ember-cli-shims": "^1.2.0", 50 | "ember-cli-sri": "^2.1.0", 51 | "ember-cli-terser": "^4.0.2", 52 | "ember-export-application-global": "^2.0.0", 53 | "ember-load-initializers": "^2.1.2", 54 | "ember-resolver": "^8.0.3", 55 | "ember-sinon": "^5.0.0", 56 | "ember-source": "~3.16.0", 57 | "ember-template-lint": "^4.4.2", 58 | "ember-try": "^2.0.0", 59 | "loader.js": "^4.7.0", 60 | "npm-run-all": "^4.1.5", 61 | "webpack": "^5.72.0" 62 | }, 63 | "engines": { 64 | "node": ">= 12" 65 | }, 66 | "ember-addon": { 67 | "configPath": "tests/dummy/config", 68 | "demoURL": "http://thefrontside.github.io/emberx-file-input/" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | useYarn: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-3.16', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~3.16.0', 15 | }, 16 | }, 17 | }, 18 | { 19 | name: 'ember-lts-3.20', 20 | npm: { 21 | devDependencies: { 22 | 'ember-source': '~3.20.5', 23 | }, 24 | }, 25 | }, 26 | { 27 | name: 'ember-lts-3.24', 28 | npm: { 29 | devDependencies: { 30 | 'ember-source': '~3.24.3', 31 | }, 32 | }, 33 | }, 34 | { 35 | name: 'ember-lts-3.28', 36 | npm: { 37 | devDependencies: { 38 | 'ember-source': '~3.28.0', 39 | }, 40 | }, 41 | }, 42 | { 43 | name: 'ember-release', 44 | npm: { 45 | devDependencies: { 46 | 'ember-source': await getChannelURL('release'), 47 | }, 48 | }, 49 | }, 50 | { 51 | name: 'ember-beta', 52 | npm: { 53 | devDependencies: { 54 | 'ember-source': await getChannelURL('beta'), 55 | }, 56 | }, 57 | }, 58 | { 59 | name: 'ember-canary', 60 | npm: { 61 | devDependencies: { 62 | 'ember-source': await getChannelURL('canary'), 63 | }, 64 | }, 65 | }, 66 | { 67 | name: 'ember-default-with-jquery', 68 | env: { 69 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 70 | 'jquery-integration': true, 71 | }), 72 | }, 73 | npm: { 74 | devDependencies: { 75 | '@ember/jquery': '^1.1.0', 76 | }, 77 | }, 78 | }, 79 | { 80 | name: 'ember-classic', 81 | env: { 82 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 83 | 'application-template-wrapper': true, 84 | 'default-async-observers': false, 85 | 'template-only-glimmer-components': false, 86 | }), 87 | }, 88 | npm: { 89 | ember: { 90 | edition: 'classic', 91 | }, 92 | }, 93 | }, 94 | embroiderSafe(), 95 | embroiderOptimized(), 96 | ], 97 | }; 98 | }; 99 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at code-of-conduct@frontside.io. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ 51 | -------------------------------------------------------------------------------- /tests/acceptance/x-file-input-test.js: -------------------------------------------------------------------------------- 1 | /* jshint expr:true */ 2 | import { run } from '@ember/runloop'; 3 | 4 | import { 5 | describe, 6 | it, 7 | beforeEach, 8 | afterEach 9 | } from 'mocha'; 10 | import { expect } from 'chai'; 11 | import Ember from 'ember'; 12 | import startApp from '../helpers/start-app'; 13 | import sinon from 'sinon'; 14 | 15 | var App; 16 | 17 | describe('Acceptance: XFileInput', function() { 18 | beforeEach(function() { 19 | App = startApp(); 20 | }); 21 | 22 | afterEach(function() { 23 | run(App, 'destroy'); 24 | }); 25 | 26 | beforeEach(function() { 27 | return visit('/'); 28 | }); 29 | 30 | beforeEach(function() { 31 | this.component = getComponentById('spec-file-input'); 32 | }); 33 | 34 | it('renders', function() { 35 | expect(this.component).not.to.be.undefined; 36 | }); 37 | 38 | it('has a custom class', function() { 39 | expect(this.component.$()).to.have.class('x-file-input'); 40 | }); 41 | 42 | it('has 0 tab index', function() { 43 | expect(this.component.$('input[type=file]')).to.have.attr('tabindex', '0'); 44 | }); 45 | 46 | it('has contains its yielded content', function() { 47 | expect(this.component.$('.spec-file-input__content :contains("Shall you upload?")')).not.to.be.empty; 48 | }); 49 | 50 | describe('With no block passed', function() { 51 | beforeEach(function() { 52 | this.blocklessComponent = getComponentById('spec-file-input-blockless'); 53 | }); 54 | 55 | it('should provide default alt text', function() { 56 | expect(this.blocklessComponent.$().text().trim()).to.equal('Upload'); 57 | }); 58 | 59 | }); 60 | 61 | 62 | describe('Bound Attributes', function() { 63 | beforeEach(function(){ 64 | this.component.setProperties({ 65 | disabled: true, 66 | multiple: true, 67 | name: "taco-cat", 68 | accept: "image/jpg" 69 | }); 70 | }); 71 | 72 | it('binds disabled attribute on the native file input', function() { 73 | expect(this.component.$('input[type=file]')).not.to.be.enabled; 74 | }); 75 | 76 | it('binds the disabled css class on the whole component', function() { 77 | expect(this.component.$()).to.have.class('x-file-input--disabled'); 78 | }); 79 | 80 | it('binds the multiple attribute on the native file input', function() { 81 | expect(this.component.$('input[type=file]')).to.have.attr('multiple'); 82 | }); 83 | 84 | it('binds the tabindex attribut on the native file input', function() { 85 | expect(this.component.$('input[type=file]')).to.have.attr('name', 'taco-cat'); 86 | }); 87 | 88 | it("binds the accept attribute on the native file input", function() { 89 | expect(this.component.$('input[type=file]')).to.have.attr('accept', 'image/jpg'); 90 | }); 91 | }); 92 | 93 | describe("Resetting the input", function() { 94 | 95 | /* 96 | * Since testing file inputs is basically impossible due to 97 | * security reasons, this test just asserts that when you trigger 98 | * the "change" event on the input it invokes the action & sends 99 | * the reset function with it. In that action we invoke the 100 | * `reset` method. Then we spy on that method and assert that it was 101 | * called. That's about all we can do to test this. 102 | * 103 | */ 104 | beforeEach(function() { 105 | let _this = this; 106 | this.resetComponent = getComponentById('spec-file-input-reset'); 107 | // Spy on the resetInput method 108 | this.resetContext = null; 109 | // Warning, using a fat arrow function will not work. It will set 110 | // `this.resetContext` to the mocha test runner contect every time. 111 | this.resetComponent.resetInput = sinon.spy(function() { 112 | _this.resetContext = this; 113 | }); 114 | 115 | this.resetComponent.$('.x-file--input').trigger('change'); 116 | }); 117 | 118 | it("calls the reset method", function() { 119 | expect(this.resetComponent.resetInput).to.have.been.called; 120 | }); 121 | 122 | it("has the correct context sent", function() { 123 | expect(this.resetContext).to.deep.equal(this.resetComponent); 124 | }); 125 | }); 126 | }); 127 | 128 | function getComponentById(id) { 129 | var registry = App.__container__.lookup('-view-registry:main'); 130 | if (registry) { 131 | return registry[id]; 132 | } else { 133 | return Ember.View.views[id]; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by create-github-actions-setup-for-ember-addons. 2 | # 3 | # You can upgrade the GitHub Actions workflow to the latest blueprints used 4 | # by Create GitHub Actions setup for Ember Addons by running it again: 5 | # 6 | # - `yarn create github-actions-setup-for-ember-addons` if using yarn and 7 | # - `npm init github-actions-setup-for-ember-addons` if using NPM. 8 | # 9 | # See https://github.com/jelhan/create-github-actions-setup-for-ember-addon for 10 | # details. 11 | # 12 | # The following lines contain the configuration used in the last run. Please do 13 | # not change them. Doing so could break upgrade flow. 14 | # 15 | #$ browsers: 16 | #$ - Chrome 17 | #$ emberTryScenarios: 18 | #$ - scenario: ember-lts-3.20 19 | #$ - scenario: ember-lts-3.24 20 | #$ - scenario: ember-release 21 | #$ - scenario: ember-beta 22 | #$ - scenario: ember-canary 23 | #$ - scenario: ember-default-with-jquery 24 | #$ - scenario: ember-classic 25 | #$ - scenario: embroider-safe 26 | #$ - scenario: embroider-optimized 27 | #$ nodeVersion: '12' 28 | #$ packageManager: yarn 29 | # 30 | 31 | name: CI 32 | 33 | on: 34 | push: 35 | branches: 36 | - master 37 | pull_request: 38 | 39 | env: 40 | NODE_VERSION: '14' 41 | 42 | jobs: 43 | lint: 44 | name: Lint 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v2 48 | with: 49 | fetch-depth: 1 50 | 51 | - uses: actions/setup-node@v2 52 | with: 53 | node-version: '${{ env.NODE_VERSION }}' 54 | 55 | - name: Get package manager's global cache path 56 | id: global-cache-dir-path 57 | run: echo "::set-output name=dir::$(yarn cache dir)" 58 | 59 | - name: Cache package manager's global cache and node_modules 60 | id: cache-dependencies 61 | uses: actions/cache@v2 62 | with: 63 | path: | 64 | ${{ steps.global-cache-dir-path.outputs.dir }} 65 | node_modules 66 | key: ${{ runner.os }}-${{ matrix.node-version }}-${{ 67 | hashFiles('**/yarn.lock' 68 | ) }} 69 | restore-keys: | 70 | ${{ runner.os }}-${{ matrix.node-version }}- 71 | - name: Install Dependencies 72 | run: yarn install --frozen-lockfile 73 | if: | 74 | steps.cache-dependencies.outputs.cache-hit != 'true' 75 | - name: Lint 76 | run: yarn lint 77 | 78 | 79 | test: 80 | name: Tests 81 | runs-on: ${{ matrix.os }} 82 | needs: lint 83 | 84 | strategy: 85 | matrix: 86 | os: [ubuntu-latest] 87 | browser: [Chrome] 88 | 89 | steps: 90 | - uses: actions/checkout@v2 91 | with: 92 | fetch-depth: 1 93 | 94 | - uses: actions/setup-node@v2 95 | with: 96 | node-version: '${{ env.NODE_VERSION }}' 97 | 98 | - name: Get package manager's global cache path 99 | id: global-cache-dir-path 100 | run: echo "::set-output name=dir::$(yarn cache dir)" 101 | 102 | - name: Cache package manager's global cache and node_modules 103 | id: cache-dependencies 104 | uses: actions/cache@v2 105 | with: 106 | path: | 107 | ${{ steps.global-cache-dir-path.outputs.dir }} 108 | node_modules 109 | key: ${{ runner.os }}-${{ matrix.node-version }}-${{ 110 | hashFiles('**/yarn.lock' 111 | ) }} 112 | restore-keys: | 113 | ${{ runner.os }}-${{ matrix.node-version }}- 114 | - name: Install Dependencies 115 | run: yarn install --frozen-lockfile 116 | if: | 117 | steps.cache-dependencies.outputs.cache-hit != 'true' 118 | - name: Test 119 | run: yarn test:ember --launch ${{ matrix.browser }} 120 | 121 | 122 | floating-dependencies: 123 | name: Floating Dependencies 124 | runs-on: ${{ matrix.os }} 125 | needs: lint 126 | 127 | strategy: 128 | matrix: 129 | os: [ubuntu-latest] 130 | browser: [Chrome] 131 | 132 | steps: 133 | - uses: actions/checkout@v2 134 | with: 135 | fetch-depth: 1 136 | 137 | - uses: actions/setup-node@v2 138 | with: 139 | node-version: '${{ env.NODE_VERSION }}' 140 | 141 | - name: Get package manager's global cache path 142 | id: global-cache-dir-path 143 | run: echo "::set-output name=dir::$(yarn cache dir)" 144 | 145 | - name: Cache package manager's global cache and node_modules 146 | id: cache-dependencies 147 | uses: actions/cache@v2 148 | with: 149 | path: | 150 | ${{ steps.global-cache-dir-path.outputs.dir }} 151 | node_modules 152 | key: ${{ runner.os }}-${{ matrix.node-version }}-floating-deps 153 | restore-keys: | 154 | ${{ runner.os }}-${{ matrix.node-version }}- 155 | - name: Install Dependencies 156 | run: yarn install --no-lockfile --non-interactive 157 | 158 | - name: Test 159 | run: yarn test:ember --launch ${{ matrix.browser }} 160 | 161 | 162 | try-scenarios: 163 | name: Tests - ${{ matrix.ember-try-scenario }} 164 | runs-on: ubuntu-latest 165 | continue-on-error: true 166 | needs: test 167 | 168 | strategy: 169 | fail-fast: true 170 | matrix: 171 | ember-try-scenario: [ 172 | ember-lts-3.16, 173 | ember-lts-3.20, 174 | ember-lts-3.24, 175 | ember-lts-3.28, 176 | ember-release, 177 | ember-beta, 178 | ember-canary, 179 | ember-default-with-jquery, 180 | ember-classic, 181 | embroider-safe, 182 | embroider-optimized 183 | ] 184 | 185 | steps: 186 | - uses: actions/checkout@v2 187 | with: 188 | fetch-depth: 1 189 | 190 | - uses: actions/setup-node@v2 191 | with: 192 | node-version: '${{ env.NODE_VERSION }}' 193 | 194 | - name: Get package manager's global cache path 195 | id: global-cache-dir-path 196 | run: echo "::set-output name=dir::$(yarn cache dir)" 197 | 198 | - name: Cache package manager's global cache and node_modules 199 | id: cache-dependencies 200 | uses: actions/cache@v2 201 | with: 202 | path: | 203 | ${{ steps.global-cache-dir-path.outputs.dir }} 204 | node_modules 205 | key: ${{ runner.os }}-${{ matrix.node-version }}-${{ 206 | hashFiles('**/yarn.lock' 207 | ) }} 208 | restore-keys: | 209 | ${{ runner.os }}-${{ matrix.node-version }}- 210 | - name: Install Dependencies 211 | run: yarn install --frozen-lockfile 212 | if: | 213 | steps.cache-dependencies.outputs.cache-hit != 'true' 214 | - name: Test 215 | env: 216 | EMBER_TRY_SCENARIO: ${{ matrix.ember-try-scenario }} 217 | run: node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO 218 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v1.1.2](https://github.com/thefrontside/emberx-file-input/tree/v1.1.2) (2017-03-17) 4 | [Full Changelog](https://github.com/thefrontside/emberx-file-input/compare/v1.1.1...v1.1.2) 5 | 6 | **Merged pull requests:** 7 | 8 | - The white space causes some visual inconsistency [\#50](https://github.com/thefrontside/emberx-file-input/pull/50) ([treelite](https://github.com/treelite)) 9 | 10 | ## [v1.1.1](https://github.com/thefrontside/emberx-file-input/tree/v1.1.1) (2017-01-04) 11 | [Full Changelog](https://github.com/thefrontside/emberx-file-input/compare/v1.1.0...v1.1.1) 12 | 13 | **Closed issues:** 14 | 15 | - Publish New Release Soon? [\#42](https://github.com/thefrontside/emberx-file-input/issues/42) 16 | - Action Handler [\#22](https://github.com/thefrontside/emberx-file-input/issues/22) 17 | 18 | **Merged pull requests:** 19 | 20 | - Use a plain \ instead of {{input}} [\#48](https://github.com/thefrontside/emberx-file-input/pull/48) ([cibernox](https://github.com/cibernox)) 21 | - Reinstall gh-pages addon [\#46](https://github.com/thefrontside/emberx-file-input/pull/46) ([Robdel12](https://github.com/Robdel12)) 22 | - Update changelog for 1.1.0 [\#45](https://github.com/thefrontside/emberx-file-input/pull/45) ([Robdel12](https://github.com/Robdel12)) 23 | 24 | ## [v1.1.0](https://github.com/thefrontside/emberx-file-input/tree/v1.1.0) (2016-08-30) 25 | [Full Changelog](https://github.com/thefrontside/emberx-file-input/compare/v1.0.0...v1.1.0) 26 | 27 | **Implemented enhancements:** 28 | 29 | - Action not firing when selecting the same file twice [\#23](https://github.com/thefrontside/emberx-file-input/issues/23) 30 | - Use an import strategy that supports more ember cli versions [\#41](https://github.com/thefrontside/emberx-file-input/pull/41) ([Robdel12](https://github.com/Robdel12)) 31 | - Add the ability to reset the file input [\#37](https://github.com/thefrontside/emberx-file-input/pull/37) ([Robdel12](https://github.com/Robdel12)) 32 | 33 | **Fixed bugs:** 34 | 35 | - TypeError: this.import is not a function [\#35](https://github.com/thefrontside/emberx-file-input/issues/35) 36 | - width: 0px on hidden file input is treated like not there [\#33](https://github.com/thefrontside/emberx-file-input/issues/33) 37 | - Importing emberx-file-input in an addon [\#30](https://github.com/thefrontside/emberx-file-input/issues/30) 38 | - Provide default `alt` attribute [\#21](https://github.com/thefrontside/emberx-file-input/issues/21) 39 | - Chrome CSS bug fix: make width 1px, not 0. [\#43](https://github.com/thefrontside/emberx-file-input/pull/43) ([Robdel12](https://github.com/Robdel12)) 40 | - Add default `alt` attribute & test for it [\#39](https://github.com/thefrontside/emberx-file-input/pull/39) ([Robdel12](https://github.com/Robdel12)) 41 | - Remove `ember-cli-import-polyfill` addon [\#38](https://github.com/thefrontside/emberx-file-input/pull/38) ([Robdel12](https://github.com/Robdel12)) 42 | 43 | **Closed issues:** 44 | 45 | - Usage in embedded scenario \(Wordpress Plugin Application\) [\#34](https://github.com/thefrontside/emberx-file-input/issues/34) 46 | - Setting file format [\#32](https://github.com/thefrontside/emberx-file-input/issues/32) 47 | - Warning when installed in Ember v2.6.2 [\#29](https://github.com/thefrontside/emberx-file-input/issues/29) 48 | - Overriding init without calling this.\_super is deprecated [\#27](https://github.com/thefrontside/emberx-file-input/issues/27) 49 | - Limited support \(Ember 1.13+ only\) [\#19](https://github.com/thefrontside/emberx-file-input/issues/19) 50 | - Add badges for NPM and ember-observer [\#18](https://github.com/thefrontside/emberx-file-input/issues/18) 51 | - Hidden file input styling bug in mobile Safari [\#16](https://github.com/thefrontside/emberx-file-input/issues/16) 52 | - Release on npm ? [\#2](https://github.com/thefrontside/emberx-file-input/issues/2) 53 | 54 | **Merged pull requests:** 55 | 56 | - Document the `accept` property [\#44](https://github.com/thefrontside/emberx-file-input/pull/44) ([Robdel12](https://github.com/Robdel12)) 57 | - Update ember cli 2.7 [\#40](https://github.com/thefrontside/emberx-file-input/pull/40) ([Robdel12](https://github.com/Robdel12)) 58 | - Add ember-cli-import-polyfill back \(fixes \#30\) [\#31](https://github.com/thefrontside/emberx-file-input/pull/31) ([Robdel12](https://github.com/Robdel12)) 59 | - Update ember CLI and deps [\#28](https://github.com/thefrontside/emberx-file-input/pull/28) ([Robdel12](https://github.com/Robdel12)) 60 | - Update included hook for nested addons [\#26](https://github.com/thefrontside/emberx-file-input/pull/26) ([xcambar](https://github.com/xcambar)) 61 | - Add Changelog [\#25](https://github.com/thefrontside/emberx-file-input/pull/25) ([Robdel12](https://github.com/Robdel12)) 62 | - npm and ember observer badges [\#20](https://github.com/thefrontside/emberx-file-input/pull/20) ([cowboyd](https://github.com/cowboyd)) 63 | - Fix mobile Safari styling bug for hidden file input element [\#17](https://github.com/thefrontside/emberx-file-input/pull/17) ([timiyay](https://github.com/timiyay)) 64 | 65 | ## [v1.0.0](https://github.com/thefrontside/emberx-file-input/tree/v1.0.0) (2015-10-09) 66 | **Closed issues:** 67 | 68 | - Fill out package.json [\#11](https://github.com/thefrontside/emberx-file-input/issues/11) 69 | - Add docs about how to style the input [\#7](https://github.com/thefrontside/emberx-file-input/issues/7) 70 | - Set up Travis CI builds [\#4](https://github.com/thefrontside/emberx-file-input/issues/4) 71 | 72 | **Merged pull requests:** 73 | 74 | - Add `accept` attribute to the native file input [\#15](https://github.com/thefrontside/emberx-file-input/pull/15) ([Robdel12](https://github.com/Robdel12)) 75 | - Adding a multi file input example [\#14](https://github.com/thefrontside/emberx-file-input/pull/14) ([Robdel12](https://github.com/Robdel12)) 76 | - Fill out package.json [\#13](https://github.com/thefrontside/emberx-file-input/pull/13) ([Robdel12](https://github.com/Robdel12)) 77 | - Readme updates [\#12](https://github.com/thefrontside/emberx-file-input/pull/12) ([Robdel12](https://github.com/Robdel12)) 78 | - Add docs on how to customize the css properly [\#10](https://github.com/thefrontside/emberx-file-input/pull/10) ([Robdel12](https://github.com/Robdel12)) 79 | - Add gh-pages addon [\#9](https://github.com/thefrontside/emberx-file-input/pull/9) ([Robdel12](https://github.com/Robdel12)) 80 | - Include example of firing actions from component [\#8](https://github.com/thefrontside/emberx-file-input/pull/8) ([lydiaguarino](https://github.com/lydiaguarino)) 81 | - Use label to style input [\#6](https://github.com/thefrontside/emberx-file-input/pull/6) ([Robdel12](https://github.com/Robdel12)) 82 | - Adding build status badge and small clean up [\#5](https://github.com/thefrontside/emberx-file-input/pull/5) ([Robdel12](https://github.com/Robdel12)) 83 | - Upgrade ember-cli to 1.13.8 [\#3](https://github.com/thefrontside/emberx-file-input/pull/3) ([Robdel12](https://github.com/Robdel12)) 84 | - Charge for glory [\#1](https://github.com/thefrontside/emberx-file-input/pull/1) ([lydiaguarino](https://github.com/lydiaguarino)) 85 | 86 | 87 | 88 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # emberx-file-input 2 | [![npm version](https://badge.fury.io/js/emberx-file-input.svg)](https://badge.fury.io/js/emberx-file-input) 3 | [![Ember Observer Score](http://emberobserver.com/badges/emberx-file-input.svg)](http://emberobserver.com/addons/emberx-file-input) 4 | [![Build Status](https://travis-ci.org/thefrontside/emberx-file-input.svg?branch=master)](https://travis-ci.org/thefrontside/emberx-file-input) 5 | 6 | I select files well. I select files very, very well. 7 | 8 | `x-file-input` is a tiny re-usable component which does one thing and 9 | only: binds an action to the native html file selection dialog while 10 | allowing you to render arbitrary HTML to act as the trigger for that 11 | file selector. 12 | 13 | This allows you to compose it with whatever other components and 14 | application code you need to make that perfect workflow that involves 15 | selecting files: from uploads to imports. 16 | 17 | What you do with the files once they are selected? Welp, that's 18 | between you and your app. 19 | 20 | ## Installation 21 | 22 | `ember install emberx-file-input` 23 | 24 | ## Usage 25 | 26 | Bind an action to the file input: 27 | 28 | ```handlebars 29 | {{x-file-input name="files" multiple=true action=(action "didSelectFiles") alt="Choose a File"}} 30 | ``` 31 | 32 | Whenever the user selects a file, the `didSelectfiles` action will be 33 | invoked with an array of [File][1] objects. 34 | 35 | > Note: Whether the file input is for a single file or mulitple files, 36 | > it will always return an array of `File` objects upon selection. 37 | 38 | In its blockless form, you will need to pass an `alt` attribute for 39 | the text you would like to be displayed inside the inputs label. 40 | 41 | ``` handlebars 42 | {{x-file-input alt="hello world"}} 43 | ``` 44 | 45 | When passing a block, the HTML inside the block will be used as the 46 | trigger of the file input. 47 | 48 | ```hbs 49 | {{#x-file-input multiple=true action=(action "didSelectFiles")}} 50 | I should buy a boat 51 | {{/x-file-input}} 52 | ``` 53 | 54 | Instead of that boring old stock file selector, your users will see 55 | this: 56 | 57 |
58 | I should buy a boat 59 | 60 |
61 | 62 | And don't worry, that custom trigger is a form label, so the file input remains 63 | 100% accessible. 64 | 65 | ### Configuring file formats with `accept` 66 | You can use the `accept` attribute to only allow specifc types of 67 | files. In this example we only allow `.png` & `.jpg` file types. 68 | 69 | ```hbs 70 | {{#x-file-input multiple=true action=(action "didSelectFiles") accept="image/png,image/jpg"}} 71 | I should buy a boat 72 | {{/x-file-input}} 73 | ``` 74 | 75 | 76 | ## Customizing the CSS 77 | 78 | The whole point of this component is for you to customize your inputs with CSS 79 | and make them look *much* better than the native inputs. Lets look at a simple 80 | example. 81 | 82 | Here is our component. You can see we have a custom class applied to the block 83 | called `custom-class`. We are going to use that class to apply our styles. 84 | 85 | ```hbs 86 | {{#x-file-input class="custom-class" action="uploadAPhoto"}} 87 |

Shall you upload?

88 | {{/x-file-input}} 89 | ``` 90 | 91 | In our CSS we want to target `.custom-class label` because the label is the 92 | element that we're making look nice. 93 | 94 | ```css 95 | .custom-class label { 96 | background: #34495e; 97 | padding: 10px; 98 | color: white; 99 | border-radius: 5px; 100 | } 101 | ``` 102 | 103 | This css will make our button look a little something like this: 104 | ![Custom file input styling](http://i.imgur.com/OHTMaAQ.png) 105 | 106 | We are not done yet! Since we're replicating a native input with 107 | HTML and CSS we have to make sure we replicate all of the "default" 108 | features we get when using a native file input. One of those things is a css 109 | `:hover` and `:focus` state. These are often overlooked but are critcal to add. 110 | In your CSS you need to add the following: 111 | 112 | ```css 113 | .x-file--input:focus + label, 114 | .x-file--input + label:hover { 115 | /* Apply your own hover state */ 116 | background-color: #2C3E50; 117 | } 118 | ``` 119 | 120 | And that's it! Your file input is now styled and decked to the nines! 121 | If you would like to see a real 122 | [life example checkout the demo page](http://thefrontside.github.io/emberx-file-input) 123 | 124 | ## Resetting the input 125 | 126 | To select the same file many times you need to call the `resetInput` 127 | method that's passed as an argument with the action. For example: 128 | 129 | ``` javascript 130 | actions: { 131 | myAction(files, resetInput) { 132 | // Do something with your files. 133 | // Once you're done, call the reset method: 134 | resetInput(); 135 | // Now your input is reset! 136 | } 137 | } 138 | ``` 139 | 140 | ## Acceptance Testing 141 | 142 | You can use the `selectFile` async helper in acceptance tests to simulate file uploads. 143 | 144 | First import the helper in your `test-helper.js` (or respective file that requires test dependencies) 145 | 146 | ```javascript 147 | import 'emberx-file-input/test-helpers/select-file-async' 148 | 149 | ... 150 | 151 | ``` 152 | 153 | then in your test: 154 | 155 | ```javascript 156 | visit('/'); 157 | selectFile('.x-file-input', {name: 'test.txt', type: 'text/plain'}); 158 | 159 | andThen(function() { 160 | 161 | ... 162 | ``` 163 | 164 | or if the code you're testing needs the object to be an actual file 165 | 166 | ```javascript 167 | visit('/'); 168 | 169 | const file = new Blob(['test'], {type: 'image/jpeg'}); 170 | selectFile('.x-file-input', file); 171 | 172 | andThen(function() { 173 | 174 | ... 175 | ``` 176 | 177 | The first argument is the class of your x-file-input component. The second argument is an object in place of the file normally returned. 178 | 179 | ## Unit Testing 180 | 181 | There is a separate `selectFile` helper that works in unit/component tests. 182 | 183 | ```javascript 184 | import 'emberx-file-input/test-helpers/select-file-unit' 185 | ``` 186 | 187 | then in your test: 188 | 189 | ```javascript 190 | selectFile('.x-file-input', {name: 'test.txt', type: 'text/plain'}); 191 | 192 | assert.equal($('.something').length, 1, 'Element exists!'); 193 | ``` 194 | 195 | ## EmberX 196 | 197 | emberx-file-input is part of the "missing components of ember" collectively 198 | known as emberx: 199 | 200 | * [emberx-select](https://github.com/adopted-ember-addons/emberx-select) 201 | * [emberx-file-input](https://github.com/adopted-ember-addons/emberx-file-input) 202 | 203 | 204 | [1]: https://developer.mozilla.org/en-US/docs/Web/API/File 205 | 206 | ## Release Process 207 | 208 | Every commit to master is built on CircleCI and the demo url is built 209 | using github pages. 210 | 211 | Npm releases follow semver, and are ad-hoc and controlled by project 212 | owners. 213 | 214 | 215 | ## Code of Conduct 216 | Please note that this project is released with a Contributor Code of 217 | Conduct. By participating in this project you agree to abide by its 218 | terms, which can be found in the `CODE_OF_CONDUCT.md` file in this 219 | repository. 220 | -------------------------------------------------------------------------------- /.lint-todo: -------------------------------------------------------------------------------- 1 | add|ember-template-lint|no-curly-component-invocation|7|4|7|4|f04318fd9767915cfe8059478671b55f118dba57|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 2 | add|ember-template-lint|no-implicit-this|1|24|1|24|075c0d8e6bf518d30bf46468a90fd7a8a3ac2941|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 3 | add|ember-template-lint|no-implicit-this|1|64|1|64|6ae999552a0d2dca14d62e2bc8b764d377b1dd6c|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 4 | add|ember-template-lint|no-implicit-this|2|13|2|13|07596f183f5e91b1778d5e47b2752b8d42aa763d|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 5 | add|ember-template-lint|no-implicit-this|2|35|2|35|88fc77e78f48fc9076f997582d6231dc838266b7|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 6 | add|ember-template-lint|no-implicit-this|2|57|2|57|f1f86b49f85dda13e0ab4c95c83f538d706f074f|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 7 | add|ember-template-lint|no-implicit-this|2|77|2|77|5fb801f2f30ac5b8a6671f1d04a959a51fc1671e|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 8 | add|ember-template-lint|no-implicit-this|3|14|3|14|075c0d8e6bf518d30bf46468a90fd7a8a3ac2941|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 9 | add|ember-template-lint|no-implicit-this|7|6|7|6|325562c769da3f80d0e63bb56514bc2e2723c9b5|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 10 | add|ember-template-lint|no-positive-tabindex|1|0|1|0|c1305f9a60115cda5d925f5f0b7e30825b53c643|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 11 | add|ember-template-lint|require-has-block-helper|4|8|4|8|bf562aead7b96b3369f2f96ff8c838387a268cc0|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 12 | add|ember-template-lint|no-curly-component-invocation|5|4|5|4|97c2580dd98aec46fd701eb9a98ab81ead21febb|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 13 | add|ember-template-lint|no-curly-component-invocation|12|4|12|4|a0c05bfc0accae9b23b0b43831239742a957b3ff|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 14 | add|ember-template-lint|no-curly-component-invocation|18|2|18|2|21d84480703b9f10b1bbe2331b8fccaa8cf44495|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 15 | add|ember-template-lint|no-curly-component-invocation|21|30|21|30|c2b66cfad2ffe1a265ba0b07aa7e2c16bbd986d9|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 16 | add|ember-template-lint|no-curly-component-invocation|29|2|29|2|baab54b47f5a868911922e661a0e4acfcfa7b419|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 17 | add|ember-template-lint|no-curly-component-invocation|41|2|41|2|67966d7e05ba983a05bacc0a035438a852ca6f51|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 18 | add|ember-template-lint|no-implicit-this|19|10|19|10|1d538fe2595eb641eb8ede22407dd7cc39951a62|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 19 | add|ember-template-lint|no-implicit-this|20|19|20|19|6ec81cfb17322a45e74faeb2af7c8b474aad1c6d|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 20 | add|ember-template-lint|no-implicit-this|21|32|21|32|1d538fe2595eb641eb8ede22407dd7cc39951a62|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 21 | add|ember-template-lint|no-implicit-this|32|10|32|10|451c79398635013449cd7de7c62a0a7ec56f8aed|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 22 | add|ember-template-lint|no-inline-styles|4|7|4|7|ef057772e8b72a16796a51d1fd39ef26d0ee4eb8|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 23 | remove|ember-template-lint|no-curly-component-invocation|7|4|7|4|f04318fd9767915cfe8059478671b55f118dba57|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 24 | remove|ember-template-lint|no-implicit-this|1|24|1|24|075c0d8e6bf518d30bf46468a90fd7a8a3ac2941|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 25 | remove|ember-template-lint|no-implicit-this|1|64|1|64|6ae999552a0d2dca14d62e2bc8b764d377b1dd6c|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 26 | remove|ember-template-lint|no-implicit-this|2|13|2|13|07596f183f5e91b1778d5e47b2752b8d42aa763d|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 27 | remove|ember-template-lint|no-implicit-this|2|35|2|35|88fc77e78f48fc9076f997582d6231dc838266b7|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 28 | remove|ember-template-lint|no-implicit-this|2|57|2|57|f1f86b49f85dda13e0ab4c95c83f538d706f074f|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 29 | remove|ember-template-lint|no-implicit-this|2|77|2|77|5fb801f2f30ac5b8a6671f1d04a959a51fc1671e|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 30 | remove|ember-template-lint|no-implicit-this|3|14|3|14|075c0d8e6bf518d30bf46468a90fd7a8a3ac2941|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 31 | remove|ember-template-lint|no-implicit-this|7|6|7|6|325562c769da3f80d0e63bb56514bc2e2723c9b5|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 32 | remove|ember-template-lint|no-positive-tabindex|1|0|1|0|c1305f9a60115cda5d925f5f0b7e30825b53c643|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 33 | remove|ember-template-lint|require-has-block-helper|4|8|4|8|bf562aead7b96b3369f2f96ff8c838387a268cc0|1645056000000|1647644400000|1650236400000|addon/templates/components/x-file-input.hbs 34 | remove|ember-template-lint|no-curly-component-invocation|5|4|5|4|97c2580dd98aec46fd701eb9a98ab81ead21febb|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 35 | remove|ember-template-lint|no-curly-component-invocation|12|4|12|4|a0c05bfc0accae9b23b0b43831239742a957b3ff|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 36 | remove|ember-template-lint|no-curly-component-invocation|18|2|18|2|21d84480703b9f10b1bbe2331b8fccaa8cf44495|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 37 | remove|ember-template-lint|no-curly-component-invocation|21|30|21|30|c2b66cfad2ffe1a265ba0b07aa7e2c16bbd986d9|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 38 | remove|ember-template-lint|no-curly-component-invocation|29|2|29|2|baab54b47f5a868911922e661a0e4acfcfa7b419|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 39 | remove|ember-template-lint|no-curly-component-invocation|41|2|41|2|67966d7e05ba983a05bacc0a035438a852ca6f51|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 40 | remove|ember-template-lint|no-implicit-this|19|10|19|10|1d538fe2595eb641eb8ede22407dd7cc39951a62|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 41 | remove|ember-template-lint|no-implicit-this|20|19|20|19|6ec81cfb17322a45e74faeb2af7c8b474aad1c6d|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 42 | remove|ember-template-lint|no-implicit-this|21|32|21|32|1d538fe2595eb641eb8ede22407dd7cc39951a62|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 43 | remove|ember-template-lint|no-implicit-this|32|10|32|10|451c79398635013449cd7de7c62a0a7ec56f8aed|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 44 | remove|ember-template-lint|no-inline-styles|4|7|4|7|ef057772e8b72a16796a51d1fd39ef26d0ee4eb8|1645056000000|1647644400000|1650236400000|tests/dummy/app/templates/application.hbs 45 | add|ember-template-lint|no-curly-component-invocation|7|4|7|4|f04318fd9767915cfe8059478671b55f118dba57|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 46 | add|ember-template-lint|no-implicit-this|1|24|1|24|075c0d8e6bf518d30bf46468a90fd7a8a3ac2941|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 47 | add|ember-template-lint|no-implicit-this|1|64|1|64|6ae999552a0d2dca14d62e2bc8b764d377b1dd6c|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 48 | add|ember-template-lint|no-implicit-this|2|13|2|13|07596f183f5e91b1778d5e47b2752b8d42aa763d|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 49 | add|ember-template-lint|no-implicit-this|2|35|2|35|88fc77e78f48fc9076f997582d6231dc838266b7|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 50 | add|ember-template-lint|no-implicit-this|2|57|2|57|f1f86b49f85dda13e0ab4c95c83f538d706f074f|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 51 | add|ember-template-lint|no-implicit-this|2|77|2|77|5fb801f2f30ac5b8a6671f1d04a959a51fc1671e|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 52 | add|ember-template-lint|no-implicit-this|3|14|3|14|075c0d8e6bf518d30bf46468a90fd7a8a3ac2941|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 53 | add|ember-template-lint|no-implicit-this|7|6|7|6|325562c769da3f80d0e63bb56514bc2e2723c9b5|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 54 | add|ember-template-lint|no-positive-tabindex|1|0|1|0|c1305f9a60115cda5d925f5f0b7e30825b53c643|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 55 | add|ember-template-lint|require-has-block-helper|4|8|4|8|bf562aead7b96b3369f2f96ff8c838387a268cc0|1650412800000|1653004800000|1655596800000|addon/templates/components/x-file-input.hbs 56 | add|ember-template-lint|no-curly-component-invocation|5|4|5|4|97c2580dd98aec46fd701eb9a98ab81ead21febb|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 57 | add|ember-template-lint|no-curly-component-invocation|12|4|12|4|a0c05bfc0accae9b23b0b43831239742a957b3ff|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 58 | add|ember-template-lint|no-curly-component-invocation|18|2|18|2|21d84480703b9f10b1bbe2331b8fccaa8cf44495|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 59 | add|ember-template-lint|no-curly-component-invocation|21|30|21|30|c2b66cfad2ffe1a265ba0b07aa7e2c16bbd986d9|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 60 | add|ember-template-lint|no-curly-component-invocation|29|2|29|2|baab54b47f5a868911922e661a0e4acfcfa7b419|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 61 | add|ember-template-lint|no-curly-component-invocation|41|2|41|2|67966d7e05ba983a05bacc0a035438a852ca6f51|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 62 | add|ember-template-lint|no-implicit-this|19|10|19|10|1d538fe2595eb641eb8ede22407dd7cc39951a62|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 63 | add|ember-template-lint|no-implicit-this|20|19|20|19|6ec81cfb17322a45e74faeb2af7c8b474aad1c6d|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 64 | add|ember-template-lint|no-implicit-this|21|32|21|32|1d538fe2595eb641eb8ede22407dd7cc39951a62|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 65 | add|ember-template-lint|no-implicit-this|32|10|32|10|451c79398635013449cd7de7c62a0a7ec56f8aed|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 66 | add|ember-template-lint|no-inline-styles|4|7|4|7|ef057772e8b72a16796a51d1fd39ef26d0ee4eb8|1650412800000|1653004800000|1655596800000|tests/dummy/app/templates/application.hbs 67 | --------------------------------------------------------------------------------