Edit on 12/6/2013: I forgot to update this post based on a bug from my earlier post. I used a for loop when forEach should have been used. I corrected this in the final code sample.
A few weeks back I wrote a blog post about adding image previews for multi-file upload controls. I didn't mention it at the time but I had an ulterior motive. A reader wrote to me a few weeks before with an interesting question.
Is it possible to use a mult-file input control and let the user select multiple times?
To be clear, what we mean here is that the user selects some files and closes the file picker dialog. She then realizes she forgot a few files and clicks to select them next.
What happens in this situation is pretty simple. Like the multiple select field, if you pick something else then the previous selection is removed. Your only option is similar to what you do for the drop down. Use ctrl/cmd to select multiple files in multiple folders all at once - and don't screw it up! Obviously most users won't be able to grok this and will screw it up, even if they know it is possible.
But my experiment had given me an idea. Remember that we can use an event handler to detect changes to the input field and get access to the file data beneath. Here is a code snippet showing this:
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/>";
}
}
Based on this, my final demo uses this code to create image thumbnails based on pictures you select. My demo has a bug though that meshes well with today's blog post. If you select images twice, the list of thumbnails grow, but the actual files associated with the control are only based on the last selection. But what if we could take those files and store them?
Before I went down this route, I updated my demo code to use AJAX to post the form. Part of the benefits of XHR2 is the ability to send file data over the wire. Let's look at a simple example of this.
<!doctype html>
<html>
<head>
<title>Proper Title</title>
<style>
#selectedFiles img {
max-width: 200px;
max-height: 200px;
float: left;
margin-bottom:10px;
}
</style>
</head>
<body>
<form id="myForm" method="post">
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");
document.querySelector('#myForm').addEventListener('submit', handleForm, false);
}
function handleFileSelect(e) {
var files = e.target.files;
for(var i=0; i<files.length; i++) {
var f = files[i];
if(!f.type.match("image.*")) {
continue;
}
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);
}
}
function handleForm(e) {
e.preventDefault();
console.log('handleForm');
var data = new FormData(document.querySelector('#myForm'));
var xhr = new XMLHttpRequest();
xhr.open('POST', 'handler.cfm', true);
xhr.onload = function(e) {
if(this.status == 200) {
console.log('onload called');
console.log(e.currentTarget.responseText);
}
}
xhr.send(data);
}
</script>
</body>
</html>
If we focus on the changes, the only real difference is that we have a submit handler for the form. We use a FormData object to package up our form and then post it to a server-side handler. The server-side code isn't terribly important. It doesn't see this as anything "special" or "Ajax-y" (my word), it is just a form post. But now the entire process runs through Ajax and not a traditional page reload. (And as a note, I'm not providing any user feedback here. In a real application I'd disable the submit button, tell the user something, etc etc.)
That parts done, now let's try storing a copy of the files. Here is my updated version with this in action.
<!doctype html>
<html>
<head>
<title>Proper Title</title>
<style>
#selectedFiles img {
max-width: 200px;
max-height: 200px;
float: left;
margin-bottom:10px;
}
</style>
</head>
<body>
<form id="myForm" method="post">
Files: <input type="file" id="files" name="files" multiple><br/>
<div id="selectedFiles"></div>
<input type="submit">
</form>
<script>
var selDiv = "";
var storedFiles = [];
document.addEventListener("DOMContentLoaded", init, false);
function init() {
document.querySelector('#files').addEventListener('change', handleFileSelect, false);
selDiv = document.querySelector("#selectedFiles");
document.querySelector('#myForm').addEventListener('submit', handleForm, false);
}
function handleFileSelect(e) {
var files = e.target.files;
var filesArr = Array.prototype.slice.call(files);
filesArr.forEach(function(f) {
if(!f.type.match("image.*")) {
return;
}
storedFiles.push(f);
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);
});
}
function handleForm(e) {
e.preventDefault();
var data = new FormData();
for(var i=0, len=storedFiles.length; i<len; i++) {
data.append('files', storedFiles[i]);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', 'handler.cfm', true);
xhr.onload = function(e) {
if(this.status == 200) {
console.log(e.currentTarget.responseText);
alert(e.currentTarget.responseText + ' items uploaded.');
}
}
xhr.send(data);
}
</script>
</body>
</html>
The changes are pretty simple. I've got a new global variable called storedFiles. When I detect a change on the input field, I now push them into this array. Finally, when the form is submitted, instead of pre-populating the FormData object we create it empty and then simply append our files. Note the append call uses the same name, files, so that when the server processes it the name is consistent.
And... believe it or not - this worked. This smells like it may be a slight security concern. I have to imagine that if browser vendors allow for this then it must be safe, but if I used this in production, I'd be real sure to let the end user know what is going on. As I said my previous demo actually implied it was doing this anyway. (I should have been clearing out my thumbnails when you selected files.) I think in that case the user would have expected it.
Archived Comments
Great post! One question, minor point? If the user chooses to select files more than once, and selected a file they'd already chosen (those silly users), would it show up more than once in the storedFiles collection? And would this actually be a problem?
I believe it would. So the fix would be to check the array before adding to it.
I was just poking at one of the very popular jQuery uploaders, and it also allows duplicates if you add more files.
Thank you that post help me finish one thing!
With the code above the thumbnails are shown but they are all named as the last file I select. Is there a problem in this code or am I missing something else?
See the linked blog entry. I made a mistake that was corrected in the earlier one but not this one. Give me a few and I'll edit this blog post too.
Corrected here. Thank you for finding this.
can we able to call the all images to server side.if possible plz give me some suggestion.I am going to implement in my project
@sabarinath: I'm sorry, but I have no idea what you are saying.
Hi,
I have used two file inputs in a single page, In this case filereader throws an error.
When I remove any one file input, filereader works fine.
Please suggest me how to use two file inputs in a single using filereader?
Thanks.
What particular error and can I see it online?
Hi,
Error is "Unable to get property '0' of undefined or null reference".
jQuery("#file-upload").fileReader();
jQuery("#file-upload").on('change', function(evt) {
// Change the node's value by removing the fake path
file_raw_data = evt.target.files[0];
});
I am using above code in two file having same filereader js.
For that reason, There is a filereader object conflict issue.
I have fixed this issue by using same class in both file input and created filereader object using that common class.
example:
<input type="file" class="cfile" id="file-upload"/>
<input type="file" class="cfile" id="file-upload2"/>
var test;
jQuery(".cfile").fileReader();
jQuery("#file-upload").on('change', function(event) {
test = event.target.files[0];
});
jQuery("#file-upload2").on('change', function(event) {
test = event.target.files[0];
});
Using above code I have fixed my issue. It is working fine. now we can use multiple file inputs in same page using filereader. but make sure having common class name.
Thanks do to reply of my query.
Regards,
Ronak Parmar
i am adding multiple image but i want to upload these files on submit button with other data....
Ok.... and?
Nothing else....
Ok, so my question is - what did you try, and what happened?
i add all my data on jquery array as the upper code is given i add all the fields in jquery variable as we do in ajax .. but the problem is that how i can use that array of files in ajax and than upload these files on server ...
i want to ask one thing that how to use that array to save these file on server.. ?
I'm trying to understand your comment in terms of this blog post. Does your question relate to my demo? If not, can you please post it on StackOverflow?
Just found your blog today, but wish I had come across it earlier! Thanks for sharing~
--
Sam Smith
Technology Evangelist and Aspiring Chef
Large file transfers made easy.
www.innorix.com/en/DS
i want to multle images in one image select the multiple images
give respnce pls
I have no idea what you are saying.
I wish you give a server side (php if possible) code on how to handle these files. I sent the data obtained but my php code says only one file has been sent.
Sorry - don't know PHP. :)
Wow !! The CSS restructuring looks good mahn.
You mean the redesign here? If so - thanks. Best 20 bucks I've spent. I'm blogging about the update in a few minutes.
Thanks a lot mahn ! Major bug in my protoype has been resolved, All thanks to you.
Glad it helped! Looks like I need to tweak the code snippets here a bit with the new design.
So awesome when a post written almost five years ago still applies! I was close to solving this issue on my own, but your post led me in the right direction with the last piece of the puzzle; to call formData.append multiple times with one file at time. (I had tried to send it the complete storedFile array.) Thanks and cheers! :-)
You are most welcome.
Hi Ramond,
my simple questions is, how to append your code with php for upload purpose.
Are you asking for the PHP version of my code? I don't know PHP - sorry.
many thanks indeed, the code is very neat and tidy. it is a really programming. it saves me a year, not just a day.
one more thing here if anyone can help? is it possible to compression the size of image before upload to the server?
I'd look to see if there is a JavaScript library that can do this. I bet one exists, so I'd suggest searching for it.
not sure whether blob file can help, the script is good, but take long time to load images to the server.
can anyone help, how to access and open the blob file stored on disk?
var f = File.createFromFileName("blob://C:/blob");
or var f = File.createFromFileName("file://C:/blob");
none of above is working, any idea? thanks
Are you trying to write to the file system? If so you can't - browsers aren't allowed to do that (for good reason).