I'm working on something a bit interesting with a multi-file upload control, but while that is in development, I thought I'd share a quick tip about working with multi-file upload controls in general.
If you are not clear about what I'm talking about, I simply mean adding the multiple attribute to the input tag for file uploads. Like so:
<input type="file" name="foo" id="foo" multiple>
In browsers that support it, the user will be able to select multiple files. In browsers that don't support it, it still works fine as a file control, but they are limited to one file. In theory, this is pretty trivial to use, but there's a UX issue that kind of bugs me. Here is a screen shot of a form using this control. I've selected two files:
Notice something? The user isn't told what files they selected. Now obviously in a form this small it isn't that big of a deal, but in a larger form the user may forget or simply want to double check before they submit the form. Unfortunately there is no way to do that. Clicking the Browse button simply opens the file picker again. Surprisingly, IE handles this the best. It provides a read-only list of what you selected:
One could use a bit of CSS to make that field a bit larger for sure and easier to read, but you get the idea. So how can we provide some feedback to the user about what files they have selected?
First, let's add a simple change handler to our input field:
document.addEventListener("DOMContentLoaded", init, false);
function init() {
document.querySelector('#files').addEventListener('change', handleFileSelect, false);
}
Next, let's write an event handler and see if we can get access to the files property of the event. Not all browsers support this, but in the ones that do, we can enumerate over them.
function handleFileSelect(e) {
if(!e.target.files) return;
var files = e.target.files;
for(var i=0; i < files.length; i++) {
var f = files[i];
}
}
The file object gives us a few properties, but the one we care about is the name. So let's create a full demo of this. I'm going to add a little div below my input field and use it as place to list my files.
<!doctype html>
<html>
<head>
<title>Proper Title</title>
</head>
<body>
<form id="myForm" method="post" enctype="multipart/form-data">
Files: <input type="file" id="files" name="files" multiple><br/>
<div id="selectedFiles"></div>
<input type="submit">
</form>
<script>
var selDiv = "";
document.addEventListener("DOMContentLoaded", init, false);
function init() {
document.querySelector('#files').addEventListener('change', handleFileSelect, false);
selDiv = document.querySelector("#selectedFiles");
}
function handleFileSelect(e) {
if(!e.target.files) return;
selDiv.innerHTML = "";
var files = e.target.files;
for(var i=0; i<files.length; i++) {
var f = files[i];
selDiv.innerHTML += f.name + "<br/>";
}
}
</script>
</body>
</html>
Pretty simple, right? You can view an example of this here: https://static.raymondcamden.com/demos/2013/sep/10/test0A.html. And here is a quick screen shot in case you are viewing this in a non-compliant browser.
Pretty simple, right? Let's kick it up a notch. Some browsers support FileReader (MDN Reference), a basic way of reading files on the user system. We could check for FileReader support and use it to provide image previews. I'll share the code first and then explain how it works.
Edit on September 11: A big thank you to Sime Vidas for pointing out a stupid little bug in my code I missed on first pass around. I made a classic array/callback bug and didn't notice it. I fixed the code and the screen shot, but if you want to see the broken code, view source on https://static.raymondcamden.com/demos/2013/sep/10/test0orig.html.
<!doctype html>
<html>
<head>
<title>Proper Title</title>
<style>
#selectedFiles img {
max-width: 125px;
max-height: 125px;
float: left;
margin-bottom:10px;
}
</style>
</head>
<body>
<form id="myForm" method="post" enctype="multipart/form-data">
Files: <input type="file" id="files" name="files" multiple accept="image/*"><br/>
<div id="selectedFiles"></div>
<input type="submit">
</form>
<script>
var selDiv = "";
document.addEventListener("DOMContentLoaded", init, false);
function init() {
document.querySelector('#files').addEventListener('change', handleFileSelect, false);
selDiv = document.querySelector("#selectedFiles");
}
function handleFileSelect(e) {
if(!e.target.files || !window.FileReader) return;
selDiv.innerHTML = "";
var files = e.target.files;
var filesArr = Array.prototype.slice.call(files);
filesArr.forEach(function(f) {
var f = files[i];
if(!f.type.match("image.*")) {
return;
}
var reader = new FileReader();
reader.onload = function (e) {
var html = "<img src=\"" + e.target.result + "\">" + f.name + "<br clear=\"left\"/>";
selDiv.innerHTML += html;
}
reader.readAsDataURL(f);
});
}
</script>
</body>
</html>
I've modified the handleFileSelect code to check for both the files array as well as FileReader. (Note - I should do this before I even attach the event handler. I just thought of that.) I've updated my input field to say it accepts only images and added a second check within the event handler. Once we are sure we have an image, I use the FileReader API to create a DataURL (string) version of the image. With that I can actually draw the image as a preview.
You can view a demo of this here: https://static.raymondcamden.com/demos/2013/sep/10/test0.html. And again, a screen shot:
Check it out and let me know what you think. As I said, it should be fully backwards compatible (in that it won't break) and works well in Chrome, Firefox, IE10, and Safari.
Archived Comments
Would be good if the preview thumbnails could just be converted to base64 and saved to the server as a thumbnail. Rather than having to upload the full size image
Data URLs are base64 with some stuff in front, so you could use that. But I'm not sure it would be better to save it to the server. Why send that data to the server when I want to preview it locally? The end goal (in the last demo) is to upload the images, not the previews. The previews are for the user's sake and don't need to be on the server, know what I mean?
Now - you may ask - what if we want to perform resizes before uploading? That could be useful for large images. That could be done with Canvas I believe. But I haven't tried that myself.
Ray,
tested demos with Chrome and Firefox: i don't see a list of thumbs, but last image i've just selected.
regards
What version of Chrome?
Chrome: 29.0.1547.66 m
Firefox: 22
Ok, FF22 is 2 behind me, I'm on beta channel. But at least it didn't break, which is good.
Chrome... are you on mobile chrome?
no, i'm on my pc with windows 7 ultimate.
if i select many images, i always see th last selected, as thumb and with its description on the right of the select file button.
Fascinating. I tested IE on Win8. So - do you mind downloading the HTML and doing some editing to see if you can figure it out yourself?
Works fine on Win 7 Pro latest FF & Chrome.
Some progress bar when uploading would be useful. Looking forward to finished script.
Progress bar for uploads is definitely possible.
Hey, I've refactored the code a bit: http://jsfiddle.net/simevid.... I'm still not satisfied since there is still DOM serialization going on (elem.innerHTML += string), but I did fix that bug where all files share the name of the last read file (see the last image in your post).
Oh lord I did the classic callback/array thing and didn't notice. Thanks. Going to fix and edit the post so folks don't copy it. While I agree about innerHTML+, imo, I wanted that aspect to be as simple and direct as possible and not direct from the main topic.
Sime - updated. Used the toArray method MDN uses in the Arguments docs. Also credited you and updated the screen shot.
Great application but
1= Is it possible to make it work in IE* and up.
2 = Is it possible to keep the file name in a var. so I could write it down in a response.write in asp.
Thank you very much in advance. Great job
@Raymond: I could fire up a VM, but I'd rather walk you through the steps. :) What did you test? The first thing would be to test input type=file/multiple and see when IE begins to suport it.
As for 2, you don't need to do that. It should be available to your server side. I know ColdFusion supports this just fine (giving you both the client file name and the server one, in case it was renamed).
This is very good example of file upload approach! But one part is missing here - how about if user needed remove one , 2 or all files from selection and select different files?
See the next post: http://www.raymondcamden.co....
In that I talk about the behavior of multiple clicks on the control. The default is to wipe away the last selection. If you use what I did in the other post, you could add a simple delete link and remove the selection from the list of stuff to upload.
Thanks a lot Raymond Camden and Sime Vidas. You guys are great!
Kindly tell me how do i deselect a file from selected list?
I've got a new demo for this - will blog it (and link it) in a bit (or tomorrow morning).
Demo: http://www.raymondcamden.co...
Its also not working on IE9
Does IE9 even support multiple w/ the input field? If not, than there is nothing I can do about it. ;) It should still let you do uploads though.
how to get the image url?
I believe I explain this in the post, right?
i mean get the img folder path
opsss.. sry i saw it~
try it now
you've mention that only can use for IE, how about for google chrome or other browser?
You mean like the filesystem url? Pretty sure it is in the initial array of data. Try dumping it to console.
Eh? When did I say this was only for IE?
where is the "myForm"page??
What do you mean? The code is above in the post, and there is a link to the demo. myForm is just the ID of the form.
Sir,i'm newbie in advance java..i'm trying to develop a project in j2ee..therefore i need help..if i ask you few questions about java,can you just guide me sir?please sir help,please sir..
I do not use Java. I know it a tiny bit, but I cannot help you with that.
Thanks a lot! It is very helpful.
Hello Raymond Camden , I have two questions:
1-As per @Šime Vidas , "he said that he is not satisfied with DOM Serialization and he fixed a bug". Is DOM serialization not good and what bug he is talking about ?
2- What is this "classic callback/array thing" ?
Thank You [I am a complete amateur in JavaScript]
1) I believe he meant where I was adding to the DOM by appending a string in a loop. This can have performance issues versus modifying the DOM one time. In *this* case, it wouldn't ever be a problem unless the user selected a large number of files, but it is something to remember.
2) I tried to find a good post on the issue and I'm not having any luck. Ahah - try this one: http://stackoverflow.com/qu...
This is very useful. But you can't select files from different directories. If one click the browse button another time the previously selected file got removed.
See my later comments - this is expected behavior.
Thank you for this code. Absolutely fantastic. Helped me out a great deal.
If your form has a reset button:
document.getElementById('myForm').addEventListener('reset', clearULForm, false);
function clearULForm(evt) {
selDiv.innerHTML = '';
}
What if I want to remove one of the images selected. Can I change the files of file input field?
How to remove selected image?
Click to select again.
That isn't possible for security reasons.
Is it possible somehow to add files/images to different folders?
Imagine I have 2 folders, Folder A and folder B.
I'd like to upload 5 images to folder A, 2 images to folder B but I also want to upload images to a non-existing folder, Folder C.
How do I do that?
Absolutely - but that's a server-side issue. Your code on that side needs to handle putting files in the right place.
How to implement loading method while displaying all the images? you know, it takes some time if you choose many images...
The same as any other loading style logic:
before you begin the async process, add a message somewhere in the DOM, "Doing stuff..."
when done - remove the message.
how to remove file from file list for example if i selected 5 file but i want remove two files from 5 files , how can i do that
See https://www.raymondcamden.c...
how to choose file from file list for example if i selected 5 file but i want add two files again, now i want to see 7 files. and seven files should inserted to database. How can i do that
I have no idea what you are asking. Can you rephrase?
I am working on Multiple image uploading .for 1st time i choose 4 images and if i click on choose again and i select 3 images. then previous images are geeting deleted and newly uploaded(3) images are displaying . but i need all the 7 images to display.
See the two followups - the second is here
https://www.raymondcamden.c...
and I link to the followup on top.
Not working in safari browser, it doesn't show image preview.
Safari is a bit behind in terms of web standards. What error do you see in the console?
hey.. i too have the same problem.. u got the solution?
Thank you for this great example.
In your completed script:
```
// ...
filesArr.forEach(function(f) {
var f = files[i];
// ...
```
The `i` variable is `undefined`. I simply removed the line and everything works fine.
Thanks - I must have forgotten to remove it.
Hi Sir Good afternoon
Sir I am facing small issue i.e., i am uploading the 5 images , after that i removed 2 images instead of 5 images and but it's inserting to the database all 5 images .. i dont want that , when i remove the 2 images the remaining 3 images should be insert.. please give me any solution
See my follow up: https://www.raymondcamden.c...
no sir you refered site it's showing onlt onthe page itself it's removing preview .. i explain clearley sir choose file 5files it's displayed , when i remove the 2 images , update the "choose file is 3 files".. and file having only 3 files
I have no idea what are you saying here. You posted on *this* blog entry, which to me implies you were using this code, which doesn't have the ability to remove files.
If you can share the URL of your code, I can take a look. Outside of that, I don't know what to tell you.
Hi Raymond, Thank you so much for this example. After little changes I made it saved my day :)
ok thank you
The link in the post (currently dated 5 months ago) below "See the two followups - the second is here" generates a 404 error, and I do not see any "and I link to the followup on top.". I want to be able to add one or more files to a list that was previously chosen (and not submitted). Can you provide an example of how to do that?
I fixed the link - but that's all I can do now.
my problem is when we upload 5 selected image after that we upload 1 image then it is add to the queue .but its save only one image which is last image which i had uploaded it. please solve my problem dear
This one does not support that. See my comment to Jens.
Maybe you are deliberate for the lesson thnx :p
I have copied this message from Filip cause i have the same feeling:
Hi Raymond, Thank you so much for this example. After little changes I made it saved my day :)
can we add delete btn to each uploaded file?
See this: https://www.raymondcamden.c...
Thanks i got the idea
when i am uploading only one file then it is showing the file name 2 times.
https://prnt.sc/hac0j9
That's the browser rendering it to the right of the control - you can't control that as far as I know.
very nice and well written but....
when post fired in $_FILES there is only one file.
Is there anything I didn't understand ?
thx
My JS code just adds a display layer - at the core it is one input/type=file tag. I'd say check the PHP code to see if it properly handles input/type=file/multiple. For example, ColdFusion didn't in version... um I don't know. But an earlier version had a bug with it.
Do you have delete image code from selected file?
See the follow up: https://www.raymondcamden.c...
When I select multiple files at one time then it works fine but how can i do this thing when upload image one by one
I do not understand your question. Can you rephrase?
when i upload multiple image at a time it working fine... but what if i select an image and upload image after that we select another image and upload for another position and so on ... for more than 30 image..
The default behavior is to wipe currently selected files when you select more images. See this example for one that handles this better: https://www.raymondcamden.c...
i think you can't understand my problem? let me explain ... I am creating a boy bio-data. where all the photo of are kept in one container for boy, his father, his mother, his sister and many more photo. Every picture have separate button for choose photo.. But when we select 1st photo (i.e boy) it upload at boys position. After that we select his father photo. then problem arise .photo of father is saved at the position of boy.. this happen for all the reaming picture.. All picture are saved only at 1st position by replacing previous photo.... Now Tell me Jquery for that... Thanks
It sounds like you have an interesting app there, but this really isn't on topic anymore for *this* blog post. I'd suggest posting to Stack Overflow.
How we can get height and with on the images when we have option to upload more then one image
There's multiple answers for this: https://stackoverflow.com/q...
Comment Deleted
merci beaucoup votre tuto ma beaucoup aide a réalisé un travail qui ma été confie ..think's
you are most welcome