Let’s face it, file inputs for file uploads are just plain ugly. Most of us will try to prettify them to make them go with the theme of our website, and in doing so, we will often hide the default implementation and simulate/trigger events on it using our own implementation as the basis.

“Simulate events you say? Looks like I need jQu..”, Slap, “Put that down!”

No, you don’t need jQuery, it’s actually relatively straight forward using Vue. Oh, and sorry for the slapstick violence, obviously there is nothing wrong with jQuery, it’s just not needed in this case.

First, let’s look at how we might make a pretty file input using bootstrap:

 <div>
    <!-- PRETTY FILE INPUT -->
    <div class="input-group">
      <input type="text" class="form-control" placeholder="Choose File..." readonly>
      <span class="input-group-btn">
        <button class="btn btn-default" type="button"><span class="glyphicon glyphicon-paperclip" aria-hidden="true"></span></button>
      </span>
    </div>
    
    <!-- REAL FILE INPUT (HIDDEN) -->
    <input type="file" style="display:none" />
  </div>

Which comes out looking like this:pretty_bootstrap_uploader

That’s much better than the default, but it doesn’t actually do anything. In order to get it to work we need to simulate a click on the hidden file input when a user clicks our paper clip symbol, so how do we go about simulating a click event?

Well, one simple way is to create a ref on our input that we can use in our component to reference the file input and click it, so:

   <!-- REAL FILE INPUT (HIDDEN) -->
    <input type="file" style="display:none" ref="file" />

Now we can reference that in a method and simulate a click, like so:

methods: {
  launchFilePicker(){
     this.$refs.file.click();
  }
}

Then we just need to bind that click event to our button:

<button class="btn btn-default" type="button" @click="launchFilePicker"><span class="glyphicon glyphicon-paperclip"></span></button>

Excellent, our button now launches the file picker window, check it out here: https://jsfiddle.net/5f2pg2om/

There’s just one problem, nothing happens when you select a file; the file window just closes. So, let’s sort that out:

Let’s start by adding a file property to our data object:

data(){
  return {
     file: ''
  }
}

Now, let’s create a custom directive that updates that value whenever a file is chosen, by adding an event listener to our hidden file input which listens for a change event:

directives: {
  uploader: {
    bind(el, binding, vnode){
      el.addEventListener('change', e => {
        if (e.target.files[0] !== undefined) {
          // vnode.context.file is directive talk for this.file
          vnode.context.file = e.target.files[0];
        }
      });
    }
  }
}

And now let’s add that to our hidden file input:

   <!-- REAL FILE INPUT (HIDDEN) -->
    <input type="file" style="display:none" ref="file" v-uploader />

Now we want to display the selected file name, so let’s add that to our input by binding value to file.name:

      <input type="text" class="form-control" placeholder="Choose File..." :value="file.name" readonly>

Finally, let’s fire our file object back to the parent using $emit, which we can do inside a watcher:

watch{
  file(){
     this.$emit('file-chosen', this.file);
  }
}

Now all we need to do is register our component and use it like so:

<upload-input @file-change="updateFile"></upload-input>

Here’s the final JSFiddle: https://jsfiddle.net/db5k05pw/

In the next article I will show you how to actually upload the file and create a progress bar component.

Advertisements