Hi I am trying to build a similar functionality to the Dropzone.js library. My problem is that even it displays the correct number of thumbnails when i submit my form it submits only the last selected items. For example in my code try to select firstly 3 images and then 3 more. It will display 6 thumbnails but in the input files its says: 3 files selected. Is there any way to make it so the input will store all files instead of the last selected?
Here is my code:
document.getElementById("authorGallery").addEventListener("change", function(event) {
var fileZone = document.querySelector(".filezone");
// Retrieve the previously selected files, if any
var selectedFiles = Array.from(fileZone.querySelectorAll(".author-gallery-image"));
var files = event.target.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
var fileItem = document.createElement("div");
fileItem.classList.add("author-gallery-image");
var thumbnail = document.createElement("img");
thumbnail.classList.add("thumbnail");
(function(fileItem, thumbnail) {
var reader = new FileReader();
reader.onload = function(e) {
var img = new Image();
img.onload = function() {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 800;
canvas.height = 800;
var width = Math.min(img.width, 800);
var height = Math.min(img.height, 800);
var x = (img.width - width) / 2;
var y = (img.height - height) / 2;
ctx.drawImage(img, x, y, width, height, 0, 0, 800, 800);
thumbnail.src = canvas.toDataURL("image/jpeg");
canvas = null;
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
})(fileItem, thumbnail);
fileItem.appendChild(thumbnail);
fileZone.appendChild(fileItem);
// Add the newly selected file to the array
selectedFiles.push(fileItem);
}
// Append the previously selected files back to the file zone
for (var j = 0; j < selectedFiles.length; j++) {
fileZone.appendChild(selectedFiles[j]);
}
});
.filezone {
display: flex;
flex-wrap: wrap;
}
.filezone .author-gallery-image {
flex-basis: 20%; /* Set the width of each item to occupy 20% of the container */
box-sizing: border-box; /* Include padding and border in the item's width */
padding: 5px; /* Add some padding around each item */
}
.author-gallery-image img {
width: 100%;
height: auto;
border-radius: 20px;
object-fit: cover;
border: 3px solid transparent;
cursor:pointer;
transition:all ease-in-out 0.2s;
}
<div id="galleryImages" class="filezone">
</div>
<div id="galleryFile">
<input type="file" id="authorGallery" name="authorgallery[]" multiple>
</div>
2
Answers
Just hide the file input, click it with a button and display number of images yourself. After the selection don’t forget to null the file input’s value so if you add deletion of images the user could select the same file again, otherwise the
change
event won’t happen.For saving the images collect them in an array and then use
FormData
andfetch()
toPOST
your images to a needed endpoint.I didn’t manage to POST it purely with the
<form>
though… I guess you should listen forformdata
event and add to the form’s formdata the image files.But better would be to upload images with an upload progress using XMLHttpRequest or a library like
axios
. Unfortunatelyfetch()
doesn’t support the upload progress (weird to have such a situation with a newer API). Uploading without a progress is not the best UX imho…Every time you select something from the file input they gets cleared. (as you have notice)
To solve this you could store all files you have selected in some variable and later update the FileList
input.files
. But do note that it ins’t possible to simply modify/update it. You need to create a FileList in a round about way…I toke the liberty of modernizing your code a bit with
const, let, createImageBitmap
to reduce some noice around having to create function closures.const/let are scoped in {} (unlike var that gets hoisted to the top