Twitter: raymondcamden


Address: Lafayette, LA, USA

Adding a file display list to a multi-file upload HTML control

09-10-2013 27,985 views JavaScript, HTML5 23 Comments

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: http://www.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 http://www.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: http://www.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.

Related Blog Entries

23 Comments

  • Commented on 09-10-2013 at 9:50 AM
    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
  • Commented on 09-10-2013 at 9:54 AM
    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.
  • Salvatore fusto #
    Commented on 09-10-2013 at 10:15 AM
    Ray,
    tested demos with Chrome and Firefox: i don't see a list of thumbs, but last image i've just selected.
    regards
  • Commented on 09-10-2013 at 10:17 AM
    What version of Chrome?
  • Salvatore fusto #
    Commented on 09-10-2013 at 10:30 AM
    Chrome: 29.0.1547.66 m
    Firefox: 22
  • Commented on 09-10-2013 at 10:38 AM
    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?
  • Salvatore fusto #
    Commented on 09-10-2013 at 11:00 AM
    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.
  • Commented on 09-10-2013 at 11:42 AM
    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?
  • Woo #
    Commented on 09-10-2013 at 11:46 AM
    Works fine on Win 7 Pro latest FF & Chrome.

    Some progress bar when uploading would be useful. Looking forward to finished script.
  • Commented on 09-10-2013 at 11:49 AM
    Progress bar for uploads is definitely possible.
  • Commented on 09-10-2013 at 10:21 PM
    Hey, I've refactored the code a bit: http://jsfiddle.net/simevidas/T5uaH/. 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).
  • Commented on 09-11-2013 at 5:23 AM
    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.
  • Commented on 09-11-2013 at 5:36 AM
    Sime - updated. Used the toArray method MDN uses in the Arguments docs. Also credited you and updated the screen shot.
  • Raymond #
    Commented on 02-10-2014 at 8:43 AM
    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
  • Commented on 02-10-2014 at 11:29 AM
    @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).
  • Roman #
    Commented on 02-12-2014 at 7:32 PM
    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?
  • Commented on 02-15-2014 at 8:19 AM
    See the next post: http://www.raymondcamden.com/index.cfm/2013/10/1/M....

    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.
  • Ansar Abbas #
    Commented on 04-13-2014 at 2:04 AM
    Thanks a lot Raymond Camden and Sime Vidas. You guys are great!
  • Ansar Abbas #
    Commented on 04-13-2014 at 2:30 AM
    Kindly tell me how do i deselect a file from selected list?
  • Commented on 04-13-2014 at 9:29 PM
    I've got a new demo for this - will blog it (and link it) in a bit (or tomorrow morning).
  • Commented on 04-14-2014 at 9:09 AM
    Demo: http://www.raymondcamden.com/index.cfm/2014/4/14/M...
  • AB #
    Commented on 09-08-2014 at 5:41 AM
    Its also not working on IE9
  • Commented on 09-08-2014 at 5:46 AM
    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.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty