Multi-File Uploads and Multiple Selects (Part 2)

A while back I wrote a simple example of using JavaScript to add file previews for a multi-file upload HTML control. You can find that entry here: Adding a file display list to a multi-file upload HTML control. I followed it up with another example (Multi-File Uploads and Multiple Selects) that demonstrated adding support for multiple selections. This weekend a reader asked for a way to remove files from the list before uploading. Here is an example of that.

First - I had to figure out how users would remove files. I could have added a button to each image preview, or a link. Anything really. But to make things simpler, I decided that a click on the image would remove it. Obviously that may not be the best UX. I added a title attribute to help make this clear. You should be able to easily modify my code to change how this works. Let's look at the code and then I'll explain the changed bits. (If you didn't read the previous entries though, please do so. I won't be going over the basics again.)

<!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 type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
	
	<script>
	var selDiv = "";
	var storedFiles = [];
	
	$(document).ready(function() {
		$("#files").on("change", handleFileSelect);
		
		selDiv = $("#selectedFiles"); 
		$("#myForm").on("submit", handleForm);
		
		$("body").on("click", ".selFile", removeFile);
	});
		
	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 = "<div><img src=\"" + e.target.result + "\" data-file='"+f.name+"' class='selFile' title='Click to remove'>" + f.name + "<br clear=\"left\"/></div>";
				selDiv.append(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);
	}
		
	function removeFile(e) {
		var file = $(this).data("file");
		for(var i=0;i<storedFiles.length;i++) {
			if(storedFiles[i].name === file) {
				storedFiles.splice(i,1);
				break;
			}
		}
		$(this).parent().remove();
	}
	</script>

</body>
</html>

The first big difference in this version is the use of jQuery. I didn't really need it before so I used querySelector instead. I needed to make use of jQuery's simple handling of post-DOM manipulation event binding (let me know if that doesn't make sense) so I added in the library. I've added my click handler here:

$("body").on("click", ".selFile", removeFile);

I then modified the image display to include the class and title attribute.

var html = "<div><img src=\"" + e.target.result + "\" data-file='"+f.name+"' class='selFile' title='Click to remove'>" + f.name + "<br clear=\"left\"/></div>";

Notice I added a div around the image and file name. This will make sense in a second. Now let's look at the handler.

function removeFile(e) {
	var file = $(this).data("file");
	for(var i=0;i<storedFiles.length;i++) {
		if(storedFiles[i].name === file) {
			storedFiles.splice(i,1);
			break;
		}
	}
	$(this).parent().remove();
}

Not really rocket science. I find the file in the existing list, remove it, and then remove the image/file text from the DOM. Done.

Archived Comments

Comment 1 by Ty Whalin posted on 4/14/2014 at 9:44 PM

I have played around several times with file uploads but have never taken the jQuery method for uploading files. I have seemed to have success with just CF. I may have to create a demo with this example you created and do some testing with it for possible future coding options. Cool, hope you had a good weekend.

Comment 2 by Chris Bowyer posted on 4/15/2014 at 4:41 AM

Slightly off topic, but I had a good play with Adobe FormsCentral last night and quite frankly I am blown back. Also got me thinking, who needs sever validation if you need JavaScript to see the form.

Comment 3 by Raymond Camden posted on 4/15/2014 at 5:08 AM

You *always* need server-side validation. Period. You need not have "friendly* server-side validation, i.e. you may just output ERROR, but you *must* check your input on the server. Period.

Let's take your example of a form that isn't even visible unless you have JS. I'd simply run the page, look at where it POSTS, and I'd try to attack your server that way.

Comment 4 by Chris Bowyer posted on 4/15/2014 at 5:22 AM

D'oh! Didn't even think about that. Suppose the same if it's just a JavaScript link to the form too.

Comment 5 by karim posted on 5/16/2014 at 10:49 PM

Détails de l’erreur de la page Web

Agent utilisateur : Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; BTRS98585; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)
Horodateur : Fri, 16 May 2014 18:43:15 UTC

Bonjour,
Félicitation pour ce programme..
Je l'ai essayer pour me faire un site ou je gère une gallerie de peintures
ce programme de download est super par contre il fonctionne sous Firefox mais sous IE8 j'ai l'erreur ci-dessous :

Message : Objet attendu
Ligne : 32
Caractère : 3
Code : 0

y à t'il une solution pour que ça marche ?

Comment 6 by Raymond Camden posted on 5/16/2014 at 11:16 PM

As I don't speak French (and I'm not sure why you would think I do as I've never used it on the blog), I can't help you.

Comment 7 by Sean Corfield posted on 5/17/2014 at 12:18 AM

Karim, try moving the document ready code to the end of that script block so the two function definitions are before it.

Comment 8 by Sean Corfield posted on 5/17/2014 at 1:25 AM

Hmm, actually, thinking about that the functions should be hoisted above the document ready stuff... right Ray?

I counted line 32 to be the document ready and just assumed the missing object error was due to that but I haven't actually tested this on IE8 (which I have on a VM)... so I should probably do that before offering any assistance :)

Comment 9 by karim posted on 5/17/2014 at 2:27 AM

Hello I'm happy that you are to give me a rapid reponce.
i don't speak English ...
excuse me if i do some error when i script.
a'm added some lines in your code and now there is not error in the new script.
But there is away no image on the screen.
The Code I'm added provide to different sites and examples for to compensate the compatibility on IE8.
I'm going to see the address of SCR perhaps I'm while find Where is my error.

Merci pour votre aide.
Cordialement.

<!doctype html>
<html>
<head>
<title>Proper Title</title>
<style>
/* #selectedFiles img {
max-width: 200px;
max-height: 200px;
float: left;
margin-bottom:10px;
}*/

#selectedFiles img {
width: 160px;
height: 120px;
border:1px solid;
float:right;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale);
}
</style>

</head>

<body>
<div id="selectedFiles"></div>
<form id="myForm" method="post">

Files: <input type="file" id="files" name="files" multiple><br/>

<input type="submit">
</form>

<!--<script type="text/javascript" src="http://ajax.googleapis.com/..."></script>-->
<script type="text/javascript" src="jquery-git1.js"></script>
<script language="javascript">
var selDiv = "";
var storedFiles = [];
//$(document).ready(function() {
jQuery(document).ready(function(){
$("#files").on("change", handleFileSelect);
selDiv = $("#selectedFiles");
$("#myForm").on("submit", handleForm);

$("body").on("click", ".selFile", removeFile);
});

/**
* Shim for "fixing" IE's lack of support (IE < 9) for applying slice
* on host objects like NamedNodeMap, NodeList, and HTMLCollection
* (technically, since host objects have been implementation-dependent,
* at least before ES6, IE hasn't needed to work this way).
* Also works on strings, fixes IE < 9 to allow an explicit undefined
* for the 2nd argument (as in Firefox), and prevents errors when
* called on other DOM objects.
*/
(function () {
'use strict';
var _slice = Array.prototype.slice;

try {
// Can't be used with DOM elements in IE < 9
_slice.call(document.documentElement);
}
catch (e) { // Fails in IE < 9
Array.prototype.slice = function (begin, end) {
var i, arrl = this.length, a = [];
// Although IE < 9 does not fail when applying Array.prototype.slice
// to strings, here we do have to duck-type to avoid failing
// with IE < 9's lack of support for string indexes
if (this.charAt) {
for (i = 0; i < arrl; i++) {
a.push(this.charAt(i));
}
}
// This will work for genuine arrays, array-like objects,
// NamedNodeMap (attributes, entities, notations),
// NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
// and will not fail on other DOM objects (as do DOM elements in IE < 9)
else {
// IE < 9 (at least IE < 9 mode in IE 10) does not work with
// node.attributes (NamedNodeMap) without a dynamically checked length here
for (i = 0; i < this.length; i++) {
a.push(this[i]);
}
}
// IE < 9 gives errors here if end is allowed as undefined
// (as opposed to just missing) so we default ourselves
return _slice.call(a, begin, end || a.length);
};
}
}());

'use strict';

// Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
if (arguments.length<=1) {
return function() {
return that.apply(owner, arguments);
};
} else {
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
};
}
};
}

// Add ECMA262-5 string trim if not supported natively
//
if (!('trim' in String.prototype)) {
String.prototype.trim= function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
};
}

// Add ECMA262-5 Array methods if not supported natively
//
if (!('indexOf' in Array.prototype)) {
Array.prototype.indexOf= function(find, i /*opt*/) {
if (i===undefined) i= 0;
if (i<0) i+= this.length;
if (i<0) i= 0;
for (var n= this.length; i<n; i++)
if (i in this && this[i]===find)
return i;
return -1;
};
}
if (!('lastIndexOf' in Array.prototype)) {
Array.prototype.lastIndexOf= function(find, i /*opt*/) {
if (i===undefined) i= this.length-1;
if (i<0) i+= this.length;
if (i>this.length-1) i= this.length-1;
for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */
if (i in this && this[i]===find)
return i;
return -1;
};
}
if (!('forEach' in Array.prototype)) {
Array.prototype.forEach= function(action, that /*opt*/) {
for (var i= 0, n= this.length; i<n; i++)
if (i in this)
action.call(that, this[i], i, this);
};
}
if (!('map' in Array.prototype)) {
Array.prototype.map= function(mapper, that /*opt*/) {
var other= new Array(this.length);
for (var i= 0, n= this.length; i<n; i++)
if (i in this)
other[i]= mapper.call(that, this[i], i, this);
return other;
};
}
if (!('filter' in Array.prototype)) {
Array.prototype.filter= function(filter, that /*opt*/) {
var other= [], v;
for (var i=0, n= this.length; i<n; i++)
if (i in this && filter.call(that, v= this[i], i, this))
other.push(v);
return other;
};
}
if (!('every' in Array.prototype)) {
Array.prototype.every= function(tester, that /*opt*/) {
for (var i= 0, n= this.length; i<n; i++)
if (i in this && !tester.call(that, this[i], i, this))
return false;
return true;
};
}
if (!('some' in Array.prototype)) {
Array.prototype.some= function(tester, that /*opt*/) {
for (var i= 0, n= this.length; i<n; i++)
if (i in this && tester.call(that, this[i], i, this))
return true;
return 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 = "<div><img src=\"" + e.target.result + "\" data-file='"+f.name+"' class='selFile' title='Click to remove'>" + f.name + "<br clear=\"left\"/></div>";
selDiv.append(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);
}

function removeFile(e) {
var file = $(this).data("file");
for(var i=0;i<storedFiles.length;i++) {
if(storedFiles[i].name === file) {
storedFiles.splice(i,1);
break;
}
}
$(this).parent().remove();
}

</script>

</body>
</html>

Comment 10 by Raymond Camden posted on 5/17/2014 at 3:42 AM

If you can, please do not post large blocks of code here. My auto formatting tends to break some of it. In the future, use a Gist or Pastebin instead. Is this online where I can see it and run it?

Comment 11 by karim posted on 5/17/2014 at 11:14 AM

OK ,

I' continuous to work on this code.. and I'm give you regularity my results about it ..

Comment 12 by mickey posted on 6/4/2014 at 7:51 PM

Hello, Raymond. Thank you for the code. I have tried to put them into my page. But I found that even the file has been removed from the list displaying in the page, the file still uploaded (or say Post) to the server. Is the reason (removed file) still contain in the input tag?

Comment 13 by mickey posted on 6/4/2014 at 9:09 PM

I now know that the handleForm is triggered by <input type="submit"> , right ? But my form is using a button to first go to a javascript to valid the form before submission. *document.getElementById("myform").submit();* I will use it to subit the form. So how can i change the method to prevent uploading the file which is removed ?

Comment 14 by Raymond Camden posted on 6/4/2014 at 9:52 PM

Without seeing your code I really can't comment. You would need to completely stop the form submit and chain off to my code after validation. If you are using other form fields then they would need to be appended too.

Comment 15 by jack posted on 6/20/2014 at 2:05 PM

Hi, i am testing your code, but it is uploading only one file from more.
Any help?

Comment 16 by Raymond Camden posted on 6/20/2014 at 5:55 PM

Put it online where I can test and I'll try and see if something stands out in the console.

Comment 17 by vinay posted on 7/5/2014 at 9:27 AM

files not removed.

var file = $(this).data("file");
alert(file);

alert shows undefined....why?

Comment 18 by Raymond Camden posted on 7/5/2014 at 6:44 PM

@vinay: I don't understand your comment. Can you add more?

Comment 19 by Rigin posted on 7/22/2014 at 11:09 AM

Am trying your code in MVC 4
But , function removeFile(e) {.... this function is deleting from temp array - storedFiles,
But while posting all the selected files are posted , Deletion NOT Working :(

Comment 20 by Rigin posted on 7/22/2014 at 11:13 AM
Comment 21 by Raymond Camden posted on 7/22/2014 at 6:32 PM

I just tested my code again in Chrome and it worked correctly. Your code does not match mine, so I'd recommend updating it.

Comment 22 by Rigin posted on 7/23/2014 at 12:06 PM

Hi,
i tried , Am using asp MVC :
from my code : xhr.send(data);
is not getting fired to my controller action.
What could be possible reason ?

Comment 23 by Raymond Camden posted on 7/23/2014 at 5:00 PM

Open up the developer console on your browser and see if an error is being reported. You can also see if the submit handler is firing correctly.

Comment 24 by Rigin posted on 7/23/2014 at 5:09 PM

Hi Raymond,

Your code is working fine.Error was at my end.
Thanks for the same

I have one more issue.

I need to post the form data also.I am using mvc4.
If i comment the e.preventdefault() the form data gets posted, but the attachments that i deleted is not removed.

How can this be done.(Post the form data as well as upload the attachments with the remove functionality intact)

Please refer the code snippet
http://pastebin.com/Pi7NTyMP

Regards,
Rigin

Comment 25 by Raymond Camden posted on 7/23/2014 at 5:21 PM

"If i comment the e.preventdefault() the form data gets posted, but the attachments that i deleted is not removed."

Ok, so then the remove action is not working for you. Think about it then - how would you debug this? I'd add console.log messages to the remove action to ensure that the delete is working properly. You can check the length of the array for example to ensure it is one less.

Comment 26 by Rigin posted on 7/23/2014 at 5:46 PM

After removing one action, the posted all files are getting uploaded ... removed one also.
Then how can we get the updated list of attachments in action ? can you brief please

Comment 27 by Raymond Camden posted on 7/23/2014 at 6:01 PM

I do not know what you mean - "by removing one action".

Comment 28 by Rigin posted on 7/23/2014 at 7:14 PM

See what I meant is : am getting all the files that we selected in the input file element in my action method .
But the model am that received in action is empty one.

How can I get the model from form where am firing this submit ? I also need the other form data..textbox, dropdown and all...
are you clear with my query ? please let me know

Comment 29 by Raymond Camden posted on 7/23/2014 at 7:46 PM

"But the model am that received in action is empty one."
I do not understand what you mean here. If you use your Dev Tools and see all the files being posted (which you *can* do with Chrome's Network tools), then the issue is with your server-side code and I can't help you with that.

"How can I get the model from form where am firing this submit ? I also need the other form data..textbox, dropdown and all..."
In my example, I create a form post consisting of JUST the files. You can add additional file fields too. See the docs for FormData: https://developer.mozilla.o...

Comment 30 by Rigin posted on 7/24/2014 at 5:07 PM

thanks for your help :)

Comment 31 by Mohamed Aref posted on 7/28/2014 at 2:09 AM

Hi, thanks a lot for you help.

I'm developing a CMS where the user can select multiple files to upload, but the problem that I faced is I can't remove a certain file from the multiple files.

I know that the input file is readonly so we don't have any control on it :(

You did that, but I don't want to use ajax, So Is there a way to remove a certain file from the selected files ?

Comment 32 by Raymond Camden posted on 7/28/2014 at 6:14 AM

Not that I know of. I'm not saying it is impossible, but I don't think you can. You could consider a Flash based widget.

Comment 33 by Mohamed Aref posted on 7/31/2014 at 4:10 AM

I don't understand you Raymond

Comment 34 by Raymond Camden posted on 7/31/2014 at 5:07 AM

Not sure how else I can say it. There are upload controls built with Adobe Flash. If you Google, you can find a bunch.

Comment 35 by Ajeet Singh posted on 9/11/2014 at 4:48 PM

i used your code all thing about select multiple image and uploading is good but there is an issue, for Example: when i select 5 image the preview of five image are appear. and when i delete 2 image to click on image and save the form then in database all five images are stored. that is a wrong thaing.
So can you provide the solution for that on my email id which i used in that post.

Thanks

Comment 36 by Raymond Camden posted on 9/11/2014 at 5:27 PM

It should work and if it does not, please show me where it is online and I will try to test there.

Comment 37 by ATMCgrafik posted on 9/25/2014 at 6:19 PM

hi my friends, nice plugin, but when I delete picture stay in value it. how edit this problem. thank you again

Comment 38 by Raymond Camden posted on 9/25/2014 at 7:24 PM

Please see my last reply. If you can share the URL, I'll take a look. Also, what browser did you test with?

Comment 39 by ATMCgrafik posted on 9/26/2014 at 4:38 PM

what I mean, your code if I delete picture by clicking, I want to delete it from choseen file of browser, at the same time.
EXAMPLE;
First;
Choseen File 5 Files
after deleting picture;
Choseen File 5 Files

I want;
Choseen File 4 Files
If I delete a few

Comment 40 by Raymond Camden posted on 9/26/2014 at 6:47 PM

The FIle Picker control is only for the CURRENT selection. Once you dismiss it and return, it will not show you previously selected items.

Comment 41 by imen posted on 10/18/2014 at 2:33 PM

S'il vous plaît, j'ai un problème dans <form id="myForm" method="post" action="test.html">

Files: <input type="file" id="files" name="files" multiple><br/>

<div id="selectedFiles"></div>

<input type="submit">
</form>

n ' envoyer pas les image

Comment 42 by imen posted on 10/18/2014 at 2:34 PM

Please, I have a problem in <form id = "myForm" method = "post" action = "test.html">

         Files: <input type = "file" id = "files" name = "files" multiple> <br/>

         <div id = "selectedFiles"> </ div>

         <input type = "submit">
</ form>

do not send the picture

Comment 43 by Raymond Camden posted on 10/19/2014 at 1:47 PM

Do you see an error in the console?

Comment 44 by imen posted on 10/19/2014 at 6:52 PM

XMLHttpRequest cannot load file:///C:/Users/Imen/Desktop/photo/handler.cfm. Cross origin requests are only supported for protocol schemes: http, data, chrome-extension, https, chrome-extension-resource. test3.html:1

Uncaught NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'file:///C:/Users/Imen/Desktop/photo/handler.cfm'. test3.html:80

Comment 45 by Raymond Camden posted on 10/20/2014 at 6:03 AM

Are you testing with a local web server or just by opening the file? You should test with a web server.

Comment 46 by imen posted on 10/20/2014 at 2:19 PM

I testing with a local web server

Failed to load resource: the server responded with a status of 404 (Not Found) http://localhost/imen/projet/handler.cfm

Comment 47 by Raymond Camden posted on 10/22/2014 at 1:23 AM

So the next question is, does handler.cfm exist?

Comment 48 by imen posted on 10/22/2014 at 11:41 AM

yes

Comment 49 by Raymond Camden posted on 10/23/2014 at 1:55 AM

You should double check then. The error says it does not exist. If you open your browser to that URL, what do you get?

Comment 50 by David Griffiths posted on 12/7/2014 at 9:02 AM

Hi Raymond, thank you for this very useful information. I have managed to get the handleForm's output data into a controller where I manipulate the images.The problem is I can't then send the images to a view with "View::make ->with " . No error messages but Laravel refuses to do it. Do I need to use return Response::json([]) and can you advise me how I could do this.

Comment 51 (In reply to #50) by Raymond Camden posted on 12/7/2014 at 2:01 PM

I'm sorry, but I don't even know what Laravel is.

Comment 52 (In reply to #51) by David Griffiths posted on 12/8/2014 at 3:25 AM

Sorry, it was presumptuous of me to assume that you would. Looking at your code in handleForm I can take the FormData object and have lots of fun with the images in a PHP code block but when I try to export the images to another page the fun stops. The same setup without jQuery works fine so I am assuming jQuery needs something returned (like with Response::json()). Just wondered if you could give me an idea how to implement that with your code in handleForm. Thanks.

Comment 53 (In reply to #52) by Raymond Camden posted on 12/8/2014 at 11:38 AM

Um... so to be clear. My code works to *post* to your server, but you don't know how to respond back to it to let it know that... it did something? Not quite sure I get what you mean. Also, I don't really know PHP. If your server side code needs to return a message to the client side code, you should probably reply with a JSON encoded msg. How that is done in PHP is not something I can help with.

Comment 54 by Desperant posted on 1/10/2015 at 6:57 PM

Hi, how can i read storedFiles array with files in PHP? Thanks a lot

Comment 55 (In reply to #54) by Raymond Camden posted on 1/10/2015 at 6:59 PM

You can't - it is a client-side variable. You can use XHR to send JS data to the server of course.

Comment 56 by Dipok Chakraborty posted on 1/18/2015 at 8:57 AM

I tried to apply this technique in my module but after remove the selected file and submit the form then all of files are uploaded including removed files. i think my form is not update after remove file. how can i fix it?

Comment 57 (In reply to #56) by Raymond Camden posted on 1/18/2015 at 3:31 PM

Is it online where I can test it? Do you see an error in the console?

Comment 58 (In reply to #57) by Dipok Chakraborty posted on 1/22/2015 at 11:45 AM

Sorry for my late. no its on my local server. if you want i can send you by mail.

Comment 59 (In reply to #58) by Raymond Camden posted on 1/22/2015 at 11:48 AM

Typically code review of this sort is a paid engagement only. If you put it online though I don't mind giving it a quick look. I'd suggest looking in the console to see if you see an error.

Comment 60 (In reply to #56) by Dipok Chakraborty posted on 1/22/2015 at 11:49 AM

How can i get files content after delete from #files

Comment 61 (In reply to #60) by Raymond Camden posted on 1/22/2015 at 11:51 AM

Not sure what you mean in this comment. To the first comment, I'd look for an error in the console.

Comment 62 (In reply to #61) by Dipok Chakraborty posted on 1/22/2015 at 11:56 AM

before append data, there is any way to know how many files in my input field

Comment 63 (In reply to #62) by Raymond Camden posted on 1/22/2015 at 12:44 PM

Yes - it is an array and you can check the length.

Comment 64 (In reply to #63) by Dipok Chakraborty posted on 1/22/2015 at 12:49 PM

how?

Comment 65 (In reply to #64) by Raymond Camden posted on 1/22/2015 at 12:59 PM

Given X is an array, in JS you can just do X.length.

Comment 66 by Phuc Tran - Allen posted on 9/17/2015 at 9:53 PM

Hi, I tried with your code and it works for deleting image per click but on submit, that removed images appear on the page (as they were added to the database and loaded for display). Any workaround?

Comment 67 (In reply to #66) by Raymond Camden posted on 9/17/2015 at 9:55 PM

What database?

Comment 68 (In reply to #67) by Phuc Tran - Allen posted on 9/17/2015 at 10:06 PM

I'm using it on a Mantis system, PHP webApp with MySQL database. Here is the screen cast about that.
https://youtu.be/UFgwnRvIApE

Comment 69 (In reply to #68) by Raymond Camden posted on 9/17/2015 at 10:20 PM

Interesting. Chrome definitely held on to the files. I'll have to think about this and get back to you.

Comment 70 (In reply to #68) by Raymond Camden posted on 9/17/2015 at 10:30 PM

So yep - you found a pretty major bug with my implementation. I don't remove the File object from the FileList value on the input field. In CHrome and Firefox, this is read only. There is a solution though - you switch to using XHR2 to upload. I'll put this in my queue to update.

Comment 71 (In reply to #69) by Phuc Tran - Allen posted on 9/17/2015 at 10:32 PM

I figure if I add this line <script>function toArray(o) { return [].slice.call(o) }</script> before calling ajax.googleapis jquery.min.js and your code. I can have right images stored and displayed for bug review (MantisBT is bug tracking webApp).

But I couldn't do away with photos remained on screen even after selecting another batch of photos. (1:39 -1:41 in my linked youtube video). In other word, I would like to clean up all preview photos whenever user starts over by clicking Choose files... and show them new preview photos as they have selected a new batch. How to do so, sir?
Thanks

Comment 72 (In reply to #71) by Raymond Camden posted on 9/17/2015 at 10:33 PM

So - the issue you'll have even with using XHR2 to post is that the UI will still say "N files". So I'm digging more.

Comment 73 (In reply to #70) by Phuc Tran - Allen posted on 9/17/2015 at 10:45 PM

Here is the fix:
// slide the array call.
+ <script>function toArray(o) { return [].slice.call(o) }</script>
<script type="text/javascript" src="http://ajax.googleapis.com/..."></script>

function handleFileSelect(e) {
+ selDiv.empty();
var files = e.target.files;
...}

// in handleFileSelect, you empty the element selDiv, so it starts fresh every time user clicks Choose Files/Images to upload...

Cheers,
Get back to me if it resolves your major bug?

Comment 74 (In reply to #73) by Raymond Camden posted on 9/17/2015 at 11:04 PM

This isn't going to work - the list of files associated w/ the input field is read only. You can't slice it.

Comment 75 (In reply to #74) by Phuc Tran - Allen posted on 9/18/2015 at 3:20 PM

Okay, once you figure it out, please keep me posted.

Comment 76 (In reply to #72) by Raymond Camden posted on 9/20/2015 at 5:35 PM

Ok, so update. I actually forgot I was using XHR2 to push. The issue is *just* the label on the file input field. If you do this:

e.target.value='';

in handleFileSelect, it makes the label around the field switch to No files selected. That *could* be confusing - but it is better than it showing X files when you had picked X and then removed one.

To be clear, my code *as is* only has the confusing label. I can confirm that when you submit, it works perfectly well.

Comment 77 by rudi posted on 2/8/2016 at 6:50 PM

hi sir, i'm using your code for my project. i think the problem are same as the other on the comment below..

1st i pick 3 picture
then i delete 1 picture
and lastly i submit the picture

and the result all the picture including the 1 that i delete before submit uploaded.

what i want to know is, is there any way that i can do so just the remaining picture uploaded and which i delete removed.

i attach the picture bellow. the code all the same as you posted up there nothing changed just adding submit function.

thanks for your time. this code is so cool if it can work properly.

Comment 78 (In reply to #77) by Raymond Camden posted on 2/8/2016 at 8:38 PM

Is it online where I can see?

Comment 79 (In reply to #78) by rudi posted on 2/10/2016 at 6:04 PM

not online yet sir..

so i make it like this..

function removeFile(e) {
selInput.empty();
var inpuTan = '';
var file = $(this).data("file");
for(var i=0;i<storedfiles.length;i++) {="" if(storedfiles[i].name="==" file)="" {="" storedfiles.splice(i,1);="" break;="" }="" }="" $.each(storedfiles,function(key,val){="" inputan="" +="val.name+',';" });="" selinput.val(inputan);="" $(this).parent().remove();="" }="" then="" at="" the="" controller="" before="" i="" save,="" i="" will="" loop="" it="" to="" check="" which="" removed="" which="" not="" then="" save="" the="" data="" tha="" i="" need="" to="" the="" database..="" :d="" i="" just="" dont="" know="" how="" to="" delete="" the="" stored="" files..="">

Comment 80 (In reply to #79) by rudi posted on 2/10/2016 at 6:06 PM
Comment 81 (In reply to #80) by Raymond Camden posted on 2/10/2016 at 7:40 PM

Nothing sticks out. If you can get it online so I can run it, let me know. Also look for errors in the console.

Comment 82 by Biky Bist posted on 2/19/2016 at 1:20 PM

its all cool code mate. I implement this, and all works at one time. Thanks Sir for this code. Its been 1-2 weeks I have been searching this feature in a simple way(deleting preview as well as its value).

Comment 83 (In reply to #82) by Raymond Camden posted on 2/19/2016 at 1:58 PM

Glad it worked for you. If you can bill a client, hit up my Amazon Wish List. ;)

Comment 84 by Germano posted on 3/25/2016 at 12:56 PM

Hi Raymond, i found your code very useful thanks for worth sharing. Please help on how to send an image on a file system and storing the image path on mysql database?
Your help will be appreciated.

Comment 85 (In reply to #84) by Raymond Camden posted on 3/25/2016 at 1:16 PM

My code should work as is for images. As for storing the image path, your form processor would handle that.

Comment 86 by Germano posted on 3/25/2016 at 1:55 PM

Thanks Sir for your quick response, real appreciate for this. I am new in php and jquery and struggling to learn. For the first part of storing on the file system works fine but can you write a code for the second part i.e storing image path in mysql database.

From this code:

form id="myForm" method="post">

Files: <input type="file" id="files" name="files" multiple="">

<div id="selectedFiles"></div>

<input type="submit">
</form>
how can i post the file path to the database since the form handles multiple form attributes?

Please Help
Expecting much from you.

Comment 87 (In reply to #86) by Raymond Camden posted on 3/25/2016 at 2:05 PM

I don't know PHP, so I can't help you with that.

Comment 88 by Germano posted on 3/25/2016 at 2:13 PM

Thanks Sir.Any other possible solution for this?

Please Help

Comment 89 (In reply to #88) by Raymond Camden posted on 3/25/2016 at 2:44 PM

Sure - PHP can process forms and file uploads just fine. I don't know how to code PHP, but I know it supports this. Please see the PHP docs.

Comment 90 by Germano posted on 3/25/2016 at 2:49 PM

Thanks Sir, let me check

Comment 91 by Rolando posted on 7/27/2016 at 4:03 AM

You Nailed! fantastic, I was going nuts looking around, found many crazy ways of doing this, this was the more straight and simple way. I used this to simplify even more my code. Thank you sir!

Comment 92 (In reply to #91) by Raymond Camden posted on 7/27/2016 at 4:03 AM

You are most welcome.

Comment 93 (In reply to #86) by Rolando posted on 7/27/2016 at 6:03 AM

I can help you out, I used Mr. Camden code on a php project, and I use jquery $.ajax instead of httpRequest, posting to a php page.

Comment 94 by kathiravan posted on 7/28/2016 at 8:04 AM

how to multiple images shows in web page

Comment 95 (In reply to #94) by Raymond Camden posted on 7/28/2016 at 1:14 PM

Sorry - what?

Comment 96 by V.S.A. Sravan posted on 8/2/2016 at 5:33 AM

Hai, this is a great article, I have a small doubt, `data.append('files', storedFiles[i]); ` in this line, what does the `files` refer to? is it ID or the name?
I am getting an issue in removing the files.

Comment 97 (In reply to #96) by Raymond Camden posted on 8/2/2016 at 1:23 PM

It refers to the name of the form 'key' - such that when you post, you would look for a form field called files.

Comment 98 by Never posted on 9/7/2016 at 6:16 PM

Change Your code as

data.append('files['+i+']', storedFiles[i]);

for Multiple Upload

Comment 99 (In reply to #98) by Raymond Camden posted on 9/7/2016 at 6:18 PM

No - that's only if your app server requires it. ColdFusion, for ex, does not.

Comment 100 by Xin Guo posted on 9/28/2016 at 6:53 AM

oh,you really help me so much ! you are my hero!! Thank you so so so much!

Comment 101 by Lija posted on 10/6/2016 at 6:27 AM

The code works fantastically.. Absolutely what I was looking for.. Thanks..

Comment 102 by Mukul Kandpal posted on 5/3/2017 at 10:04 AM

hi
Raymond sir,
i want to ask you when we upload multiple fiile and preview image this is helpful for me .. thanku for this

But whenever we upload 5 seleted file then preview it and now one more image add in a queue then we insert in single image. which image is last image to insert .. thn how to save all file . Please tell us

Comment 103 (In reply to #102) by Raymond Camden posted on 5/3/2017 at 1:03 PM

I have no idea what you are saying here. Can you rephrase?

Comment 104 by Atharv posted on 5/8/2017 at 7:59 AM

Hello Sir,

Nice plugin for muliple image uploding. I wanna to ask what i will have to do if i want to send some additional data with image uploading like if each image has a image category .

Comment 105 (In reply to #104) by Raymond Camden posted on 5/8/2017 at 11:40 AM

You can just send form fields along. You would need to associate file X with form field meta value Y but it is definitely doable.

Comment 106 (In reply to #105) by Atharv posted on 5/8/2017 at 1:01 PM

https://uploads.disquscdn.c...

Thanks for quick reply. I am not getting exactly what u wanna say. I have atteched a snapshot what i want exactly. catagory select box bind with each image. Please give me some hint or example code.

Comment 107 (In reply to #106) by Raymond Camden posted on 5/8/2017 at 1:40 PM

I kinda gave you a hint already. When doing a POST with XHR, you can send form fields along that contain any data you want. Give you want to associate a dropdown with each file, you could use form fields named like so:

category1
category2

where 1 matches the first image, and so on.

Comment 108 by Abdul Ghaffar posted on 5/25/2017 at 7:08 AM

Hello Sir,
Its only show the images how I can show and remove other mime types like pdf, doc and rar etc?

Comment 109 (In reply to #108) by Raymond Camden posted on 5/25/2017 at 12:49 PM

You would need to find someway of representing them. For example, find an image that represents PDF, like maybe the logo, and use that image instead.

Comment 110 by cgeiser posted on 6/1/2017 at 5:52 PM

I'm trying to use your code to upload multiple images through PouchDB. For this to work I need to have a variable set to equal the array of images - then that variable gets submitted through PouchDB. I've got an example on jsbin that I had tried to add your code into and couldn't get that working. The link here contains the working code that allows me to submit one image, but not multiple images: http://jsbin.com/haturuk/ed...

Your code in a working jsbin: http://jsbin.com/posecek/2/...

Any chance you can provide any pointers?

Thanks!

Comment 111 (In reply to #110) by Raymond Camden posted on 6/1/2017 at 9:59 PM

Since the array of images is stored in storedFiles, you can get the length just by doing storedFiles.length. Right?

Comment 112 (In reply to #111) by cgeiser posted on 6/28/2017 at 6:07 PM

Hmm. One of the keepers of PouchDB suggested this: https://gist.github.com/nol...
But no luck.
I'll toil on...

Comment 113 by cgeiser posted on 6/28/2017 at 6:12 PM

One more question. I can't figure out how to put the images to populate the screen going across the page instead of vertically. Any chance you can think of an easy modification so that they'll go across, wrapping to the next line when the fill the width of the the div? (<div id="selectedFiles" style="max-width: 900px">)
Like this:
img1 img2 img3
img4 img5

Comment 114 (In reply to #113) by Raymond Camden posted on 6/28/2017 at 6:19 PM

That's just CSS. I'd build a new HTML file with 5 hard coded images, work on your CSS, then apply it to that.

Comment 115 (In reply to #114) by cgeiser posted on 6/28/2017 at 7:31 PM

I had tried, and failed on that route. Now I got it:
var html = "<div class="myCustImg"><img src="\""" +="" e.target.result="" +="" "\"="" data-file=""+f.name+"" class="selFile" title="Click to remove">" + "</div>";

with the class defined as:
.myCustImg {
display: inline-block;
}

Thanks for the fantastic tool!!!

Comment 116 (In reply to #115) by Raymond Camden posted on 6/28/2017 at 7:42 PM

How did it fail?

Comment 117 (In reply to #116) by cgeiser posted on 6/28/2017 at 7:46 PM

I actually can't remember what I tried before this - but it was more complicated and didn't work. This was quite simple and works perfectly!

Comment 118 (In reply to #117) by Raymond Camden posted on 6/28/2017 at 7:47 PM

Oh cool. Well if you get rich doing this, visit my Amazon Wish List please. ;)

Comment 119 (In reply to #118) by cgeiser posted on 6/28/2017 at 7:52 PM

Rich won't be in the cards I don't think. But if I do ...
:)

Comment 120 by Stefan Zlatanovic posted on 12/26/2017 at 12:45 AM

Hey, thank you for this, I've implemented this code and it works fantastic for previewing mutliple image uploads with the ability to delete and upload more, but I'm having problem with the images being submitted. I've modifed to the code to send my other inputs and that part works, but the images themselves arent being sent. I have my php side respond with whats it getting, and its getting the inputs but not images. The problem seems to be that .append isnt assigning images to the array, because it does create and send the image when its changed to a string. Here is what the server is getting when its trying to load images, and when that bit is changed to a string:
https://gyazo.com/484c38d72...
https://gyazo.com/2a3c9e4e6...
https://gyazo.com/03bdc64a7...
https://gyazo.com/abf36bd21...
Its data.append('files', storedFiles[i]); that seems to be the problem, what I can do to make it work?

Comment 121 (In reply to #120) by Raymond Camden posted on 12/26/2017 at 3:07 PM

Is this online where I can test myself? Do you see any errors in the console?

Comment 122 (In reply to #121) by Stefan Zlatanovic posted on 12/26/2017 at 7:31 PM

I was putting it online to show you, but during that I accidentally got it to work. One of the mistakes was not using a $_FILES array in php. You also have to put [] at the end of 'files' to make it into array it seems.
Here is the end result of it working with the data structure as its received:
https://i.gyazo.com/eb8460c...
https://i.gyazo.com/29224ba...
For those wondering how to call it in php, it would be $_FILES['files']['name'][0] for example, to get the name of the first one. Seems a bit counterintuitive to have the number indexes after the associative ones, but what can you do.
Either way thanks again for this implementation, saved my life on a project.

Comment 123 (In reply to #122) by Raymond Camden posted on 12/26/2017 at 7:41 PM

Ah, glad you got it!

Comment 124 by cgeiser posted on 1/11/2018 at 7:06 PM

Would there be a way to use canvas.width and canvas.height to redraw the preview image instead of CSS? The reason as that some users want to print to PDF on a mobile device - this results in the full size image being used in the PDF and huge PDF's.

I was thinking that something like below could work but I can't figure out what to replace e.target.result with to get the resized images to show in preview (var html = "<div><img src="\""" +="" e.target.result="" +="" "\"="" data-file=""+f.name+"" class="selFile" title="Click to remove">" + "</div>";)

Thanks for all your help! https://uploads.disquscdn.c...

Comment 125 (In reply to #124) by Raymond Camden posted on 1/12/2018 at 3:58 PM

I really have no idea what you mean. Are you allowing the user to select PDFs, not images

Comment 126 (In reply to #125) by cgeiser posted on 1/12/2018 at 4:47 PM

Sorry if I wasn't clear. The form that uses your code is sometimes printed out after the details are entered. Usually when printed it is printed to PDF. When this is done the underlying image data for the image present in the browser view of this form is the user selected image at it's native size, not the resized size. This creates enormous PDF files pretty easily. I believe the workaround would be to use canvas to redraw the image at lower size/resolution for the web view - then when the web view is printed it will be this lower resolution file that is printed.

Any ideas on this?

Thanks!

Comment 127 (In reply to #126) by Raymond Camden posted on 1/12/2018 at 7:27 PM

Odd that the "Print to PDF" feature is ignoring the CSS. Maybe add a width="X" to the img tag where X matches the max width used in the CSS.

Comment 128 (In reply to #127) by cgeiser posted on 1/20/2018 at 6:56 AM

Unfortunately that doesn't work either - the PDF created from the web form on iOS still embeds the full size image.

Thanks for your help!

Comment 129 (In reply to #128) by Raymond Camden posted on 1/20/2018 at 1:54 PM

Maybe look into a client-side library to resize the image. That's an option.

Comment 130 by Justine Abuel posted on 2/24/2018 at 7:54 AM

This was great man but can you help me on how can i upload it to the sql database i am using asp.net c#. thanks in advance and also how can i limit into 4 maximun of they upload

Comment 131 (In reply to #130) by Raymond Camden posted on 2/24/2018 at 9:49 AM

I've never used ASP.Net so I can't help with this. As for the max, since you can read the length of the files array, you can check for that and when it is too high, prevent form submission.

Comment 132 by richard gonzales posted on 4/11/2018 at 3:59 AM

Hi Raymond, I find this blog really helpful in my website although there's a bit of problem. whenever I remove the images, its still on the selected item not remove(only the image/s). Thanks, hoping for your help.

Comment 133 (In reply to #132) by Raymond Camden posted on 4/11/2018 at 1:05 PM

Add a console.log(storedFiles.length) before and after the splice - see if it's properly resizing.

Comment 134 (In reply to #133) by cgeiser posted on 4/12/2018 at 10:50 PM

I'm struggling to figure out where to put the console.log(storedFiles.length), can you provide any more guidance?

I've got a working demo here: http://jsbin.com/posecek/6/...

Thanks!

Comment 135 (In reply to #134) by Raymond Camden posted on 4/13/2018 at 1:25 AM

Err, before and after the splice -

storedFiles.splice(i,1) - that's in the remove handler.

Comment 136 (In reply to #134) by Raymond Camden posted on 4/13/2018 at 1:27 AM

I used your jsbin, added 3 files, removed one, and checked storedFiles.length in the console - it was 2.

Comment 137 (In reply to #135) by cgeiser posted on 4/13/2018 at 11:40 PM

Right, the console.log works. But the number of files shown next to the Browse box does not update. Is there a way to get that to update with the number of files?
The screenshot below would show the 3 images below the Browse button and above the 'Select an image' https://uploads.disquscdn.c... comment. As shown, I've selected the 3 images to remove them, but the image count shown is still 3.

Comment 138 (In reply to #137) by Raymond Camden posted on 4/14/2018 at 2:12 PM

As far as I know you can't change that, but you can maybe use CSS to hide the entire UI.

Comment 139 by Quicksilver posted on 5/8/2018 at 4:00 AM

Added fontawesome to show extension based on the selected file and removing files with delete icon will work fine, please check the console when removing files.

https://jsfiddle.net/athish...

Comment 140 (In reply to #139) by Raymond Camden posted on 5/8/2018 at 12:54 PM

So - to be clear - you are sharing your version - not asking for help, right? :)

Comment 141 (In reply to #140) by Quicksilver posted on 5/9/2018 at 2:57 AM

Yeah, just a little add-on of icons. Thanks for the fantastic code snippet, made my life easy :)

Comment 142 by abirami gnanam posted on 7/27/2018 at 2:55 AM

hai sir,
i want to display 5image using array.after displaying 5img i want to add image using some button

Comment 143 (In reply to #142) by Raymond Camden posted on 7/27/2018 at 1:11 PM

Sorry I honestly don't know what you are asking here.

Comment 144 (In reply to #139) by Pranay Pokharkar Patil posted on 9/20/2018 at 9:15 AM

Hey can you tell me how to do this in react js???

Comment 145 by Rejani posted on 8/19/2019 at 5:03 AM

Hello Sir,

Really helpful article. Thank you.

Comment 146 by manoj posted on 1/20/2020 at 10:32 AM

hello Raymond Camden when removing images its only removes the preview not from in input file.. can you please help me to delete that file from input also.. thanks in advance :)

Comment 147 by manoj posted on 1/20/2020 at 10:35 AM

here i have select 9 images from computer and then remove some images...but its still show me 9.. please help me to resolve these problem

Comment 148 (In reply to #146) by Raymond Camden posted on 1/20/2020 at 3:08 PM

Please share an online version where I can test.

Comment 149 by Rishabh Rana posted on 3/8/2020 at 1:50 PM

Great explanation. Read all previous 3 blogs. Sir i having trouble saving the images. I am using the form to send data to php page, where i upload the file into server, and then save file name in database. So i modified the code a little bit, and when i submit the form , the data being sent over to php page says after var_dump($_POST) as array(0){};

https://drive.google.com/op...