Multi-File Uploads and Multiple Selects

This post is more than 2 years old.

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.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Tamara Temple posted on 10/22/2013 at 7:22 PM

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?

Comment 2 by Raymond Camden posted on 10/22/2013 at 7:35 PM

I believe it would. So the fix would be to check the array before adding to it.

Comment 3 by Tamara Temple posted on 10/23/2013 at 7:21 AM

I was just poking at one of the very popular jQuery uploaders, and it also allows duplicates if you add more files.

Comment 4 by Mikelarg Reu posted on 11/12/2013 at 8:20 PM

Thank you that post help me finish one thing!

Comment 5 by Mathew posted on 12/6/2013 at 8:49 PM

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?

Comment 6 by Raymond Camden posted on 12/6/2013 at 8:52 PM

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.

Comment 7 by Raymond Camden posted on 12/6/2013 at 9:15 PM

Corrected here. Thank you for finding this.

Comment 8 by sabarinath posted on 3/24/2014 at 10:48 AM

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

Comment 9 by Raymond Camden posted on 3/24/2014 at 2:30 PM

@sabarinath: I'm sorry, but I have no idea what you are saying.

Comment 10 by Ronak Parmar posted on 5/15/2015 at 4:27 AM

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.

Comment 11 (In reply to #10) by Raymond Camden posted on 5/15/2015 at 11:20 AM

What particular error and can I see it online?

Comment 12 (In reply to #11) by Ronak Parmar posted on 5/23/2015 at 4:24 AM

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

Comment 13 by Moiz Arif posted on 7/27/2015 at 8:23 PM

i am adding multiple image but i want to upload these files on submit button with other data....

Comment 14 (In reply to #13) by Raymond Camden posted on 7/27/2015 at 8:46 PM

Ok.... and?

Comment 15 (In reply to #14) by Moiz Arif posted on 7/28/2015 at 3:46 PM

Nothing else....

Comment 16 (In reply to #15) by Raymond Camden posted on 7/28/2015 at 4:02 PM

Ok, so my question is - what did you try, and what happened?

Comment 17 (In reply to #16) by Moiz Arif posted on 7/28/2015 at 4:10 PM

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 ...

Comment 18 (In reply to #16) by Moiz Arif posted on 7/28/2015 at 4:12 PM

i want to ask one thing that how to use that array to save these file on server.. ?

Comment 19 (In reply to #17) by Raymond Camden posted on 7/28/2015 at 4:23 PM

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?

Comment 20 by Sam Smith posted on 6/14/2016 at 2:53 AM

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

Comment 21 by Prakash Ponnala posted on 4/9/2018 at 5:37 PM

i want to multle images in one image select the multiple images
give respnce pls

Comment 22 (In reply to #21) by Raymond Camden posted on 4/9/2018 at 6:01 PM

I have no idea what you are saying.

Comment 23 by Sanath Ks posted on 5/14/2018 at 7:59 AM

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.

Comment 24 (In reply to #23) by Raymond Camden posted on 5/14/2018 at 9:30 AM

Sorry - don't know PHP. :)

Comment 25 by Sanath Ks posted on 5/15/2018 at 5:26 AM

Wow !! The CSS restructuring looks good mahn.

Comment 26 (In reply to #25) by Raymond Camden posted on 5/15/2018 at 11:15 AM

You mean the redesign here? If so - thanks. Best 20 bucks I've spent. I'm blogging about the update in a few minutes.

Comment 27 by Sanath Ks posted on 5/15/2018 at 6:18 PM

Thanks a lot mahn ! Major bug in my protoype has been resolved, All thanks to you.

Comment 28 (In reply to #27) by Raymond Camden posted on 5/15/2018 at 6:34 PM

Glad it helped! Looks like I need to tweak the code snippets here a bit with the new design.

Comment 29 by Dan Pettersson posted on 5/16/2018 at 8:06 PM

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! :-)

Comment 30 (In reply to #29) by Raymond Camden posted on 5/16/2018 at 8:08 PM

You are most welcome.

Comment 31 by Niki posted on 1/21/2019 at 7:35 AM

Hi Ramond,
my simple questions is, how to append your code with php for upload purpose.

Comment 32 (In reply to #31) by Raymond Camden posted on 1/21/2019 at 3:14 PM

Are you asking for the PHP version of my code? I don't know PHP - sorry.

Comment 33 by lucy posted on 7/22/2019 at 10:30 PM

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?

Comment 34 (In reply to #33) by Raymond Camden posted on 7/23/2019 at 12:58 PM

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.

Comment 35 (In reply to #34) by lucy posted on 7/23/2019 at 4:01 PM

not sure whether blob file can help, the script is good, but take long time to load images to the server.

Comment 36 (In reply to #35) by lucy posted on 7/25/2019 at 10:33 AM

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

Comment 37 (In reply to #36) by Raymond Camden posted on 7/25/2019 at 12:29 PM

Are you trying to write to the file system? If so you can't - browsers aren't allowed to do that (for good reason).