File Input

by Tim McKay

HTML

<div id="app">
  <label :for="id">Attachments</label>
  <div>
    <input type="file" :id="id" :ref="id" :model="test" multiple
           class="input--file" @change="fileChange">
    <div v-if="attachments.length" class="input-field">
      <span class="input-field--trigger" @click="triggerInput(id)"></span>
      <span>{{ attachments[0].name }}</span>
      <button class="remove-file" @click="remove(id)">X</button>
    </div>
    <div v-else class="input-field" @click="triggerInput(id)">No File Selected</div>
    <button @click="triggerInput(id)">
      Select File
    </button>
  </div>
</div>

SCSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

h2 {
  font-weight: bold;
  margin-bottom: 15px;
}

.input--file {
	width: 0.1px;
	height: 0.1px;
	opacity: 0;
	overflow: hidden;
	position: absolute;
	z-index: -1;
}

.input-field {
  display: inline-block;
  position: relative;
  
  &--trigger {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    cursor: pointer;
  }
}

.remove-file {
  position: relative;
}

Vue

new Vue({
  el: "#app",
  data() {
  	return {
    	test: '',
    	attachments: '',
    }
  },
  computed: {
  	id() {
    	return 'fileInput-1';
    }
  },
  methods: {
  	fileChange(e) {
    	const files = e.target.files || e.dataTransfer.files;
      console.log(files);
      // reassign Object for reactivity
      // files.length isn't carried over in the reassign, have to manually target it
      this.attachments = Object.assign({ length: files.length }, files);
    },
    triggerInput(ref) {
    	this.$refs[ref].click();
    },
    remove(ref) {
      const fileInput = this.$refs[ref];
      fileInput.value = ''; // reset input
      fileInput.dispatchEvent(new Event('change')); // trigger change event
    }
  }
});