I had to develop a custom upload modal. So I bit the bullet, Googled "form upload file" and there it is, there's an <input type="file" />
, excellent I thought. I used it and here's what I was greeted with:
Way off from the provided design.
So, where to start? Well actually I read a blog post, which led me to a demo. With those two sources I was able to connect the dots and figure out the approach.
The basic problem
The basic problem is a simple one, but not straightforward to fix if you don't know how. The problem is that the input has pre-set text, layout and all that shebang. I can't format that stuff to follow the designs, I don't have any selectors to work with. And even if I did I'd have to do black magic to get it to look how I want it.
But thankfully it's not too problematic to solve if you know how to.
The basic solution (CSS)
Simply, hide the <input type="file" />
, and render your own version of it from the ground up, with the magic of the <label>
tag.
Hide the non-ideal default
So let's start by hiding it, this can be done simply by the following styles:
input[type="file"] {
opacity: 0;
position: absolute;
pointer-events: none;
// alternative to pointer-events, compatible with all browsers, just make it impossible to find
width: 1px;
height: 1px;
}
With those two lines you've made it invisible (not un-existent like you would with display: none;
) and given it position: absolute;
so it doesn't interfere with the document flow and pointer-events: none;
so clicking on other visible elements doesn't trigger this one (because it is technically still there, just not visible).
Being able to click it again
So, if our element is invisible, how can we click it? That's where the magic of label
comes in. Such useful things.
Our HTML would now look like so:
<input type="file" id="myuniqueid" />
<label for="myuniqueid">!!Upload file!!</label>
This allows us to click on !!Upload file!!
and trigger the upload file dialog from the browser. That is the magic of labels.
So good, then all we need to do is style this label to look like a button or whatever we want. Simple enough.
input[type="file"] + label {
// your styles here
}
The basic solution (JS)
So now we have the button looking like we want. Now we want it working like we want.
In all of the following steps we'll have the following to handle file changes (JSX syntax is assumed):
<input type="file" id="myuniqueid" onChange={handleChange} />
Where handleChange
is our function, which we'll be exploring. onChange
is triggered after the user has uploaded one or more files or has cancelled their interaction.
If user cancels upload
We need to be able to handle if the user cancels their upload interaction, this can be done simply:
function handleChange(event) {
// User cancelled
if (!event.target.files[0]) {
return
}
}
Getting the file's name
To get the file's name one does event.target.files[0].name
.
Previewing the file
Maybe we want to preview the file in our file upload element. In this case there are a couple approaches, one is to upload to Amazon S3 and then show the uploaded image, another one (which we'll do now) is to do a local preview, before an upload has taken place.
So here we go, our local previewer:
function generatePreviewImgUrl(file, callback) {
const reader = new FileReader()
const url = reader.readAsDataURL(file)
reader.onloadend = e => callback(reader.result)
}
So now our handleChange
function looks like this:
function handleChange(event) {
const file = event.target.files[0]
// User cancelled
if (!file) {
return
}
generatePreviewImgUrl(file, previewImgUrl => {
// (assuming we use React)
this.setState({ previewImgUrl })
})
}
Where later in the React component you do:
<img src={this.state.previewImgUrl} />
In closing
Hope this guide was useful. I tried to put together the core concepts in an easy reference-able guide so that you can re-use it in any environment you need. :)
The concepts are not difficult, you just need to know that one key point:
- Using hidden input + styled label for the button
Top comments (6)
Dude, no where you have mentioned where to write this code chunk,
function generatePreviewImgUrl(file, callback) {
const reader = new FileReader()
const url = reader.readAsDataURL(file)
reader.onloadend = e => callback(reader.result)
}
Even, you have nowhere initialised const url in your code.
After testing your code i am getting below data instead of image:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAABHNCSVQICAgIfAhkiAAAIABJREFUeJzsvXmYJEd1L/o7kUttXb33rJpNmkWjBbRcsRgJsQhJgC0BMsZgVj8D17425l4bf9d+z2Bh8PUzXvjs62ewsD/L1/Y1zyDggQVoQUhCIMBakIQYIYkRGs2MZnp6equ9KiPeH1lZHZ2dS2RWVnVVT/74millRpw4cfLEiXNOREYCKVKkSJEiRYoUKVKkSJEiRYoUKVIMNmi9GUgxXDh9+vSOTCazSQgxyRjLWpaVISJT0zSDiEzOualpmgkAlmU1GGMNIUTDsqymEKKhaVqdc14jotOLi4uz27Zte3a9+5RieJAarBQAQNVqdSfnfFLTtAnLss4RQuwjoj1EtJWIJoUQOgBdCGEyxkwhRIaINCGEBkADoBERY4xB13UAQKvVAuccQggOwAJgEZElhLCIqM45bxBRA
Your code seems to be buggy. please fix it and it will be great if you can attach codepen with your code.
If you're looking for how to structure your code, the Mozilla Developer Network has some great articles on starting web development and JavaScript: developer.mozilla.org/en-US/docs/L...
Hey sorry for the slow response.
That function doesn't need a particular location. It just needs to be available to the code that needs it. So it's up to you where to put it.
As I recall, that output from the function is the correct output. It encodes the image into text and tells the img tag to load it from there when you put it in its src tag.
Agree with @Pushup Singh this is incomplete as it doesnt handle the correct image url to render the preview.
Nice! This approach is very similar to how I create file upload buttons, though I don't use React. You can have fun outputting the filename of the selected file to the page as well - check out my demo here: codepen.io/tomhodgins/pen/PbMOar
I do a similar thing, I wrote about it in an article where I outlined how to style other form elements too while keeping them accessible: ashleysheridan.co.uk/blog/Accessib...