I have a file uploader that shows preview images and it is currently set so if you click an image the image preview is deleted and the image is also effectively deleted from the FileList
array before the form submission by creating a second array and setting the FileList
to hold these values. All of this works OK.
The Context
Whilst setting this up and posting this as a question on StackOverflow in order to (theortically) keep the code simpler I removed the 'remove image' button and set it so the image was deleted both visually and from the FileList if you clicked the image itself. I'm now struggling to work out how to transfer this functionality so when the 'x' is clicked the same functionality happens.
In the current code if you click the image you get the expected behaviour.
I've now included code so that the image preview for each individual image is wrapped in a parent <figure>
element with the 'x' .remove-image
element included in this too. I've also included some commented out code at the bottom that if uncommented removes the <figure>
element containing the image when the 'x' is clicked.
The Question
How do I get it so that instead of when the <img>
is clicked the required functionality only happens when the 'x' is clicked?
I would think I need to somehow have the click event on .remove-image
(the 'x') that uses the .closest
method to go up to the figure element and down to the image i.e. removeImage.closest('figure').querySelector('.img')
but I only want the required functionality when the 'x' is clicked, and not when either the 'x' or the image are clicked, which would be simple to fix.
Any help greatly appreciated.
Codepen: https://codepen.io/thechewy/pen/oNdrzjz
let attachFiles = document.getElementById("attach-files");
let previewWrapper = document.getElementById("show-selected-images");
let form = document.getElementById("upload-images-form");
let submitData = new DataTransfer();
attachFiles.addEventListener("change", (e) => {
const currentSubmitData = Array.from(submitData.files);
// For each addded file, add it to submitData if not already present
[...e.target.files].forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
// Sync attachFiles FileList with submitData FileList
attachFiles.files = submitData.files;
// Clear the previewWrapper before generating new previews
previewWrapper.replaceChildren();
// Generate a preview <img> for each selected file
[...submitData.files].forEach(showFiles);
});
function showFiles(file) {
let uploadImageWrapper = document.createElement("figure");
let previewImage = new Image();
let removeImage = `<div > X </div>`;
// Set relevant <img> attributes
previewImage.dataset.name = file.name;
previewImage.classList.add("img");
previewImage.src = URL.createObjectURL(file);
// Adds click event listener to <img> preview (this needs moving to the .remove-image element)
previewImage.addEventListener("click", (e) => {
const target = e.currentTarget;
const name = target.dataset.name;
// Remove the clicked file from the submitData
[...submitData.files].forEach((file, idx) => {
if (file.name === name) {
submitData.items.remove(idx);
}
});
// Reset the attachFiles FileList
attachFiles.files = submitData.files;
// Remove the <img> node from the DOM
target.remove();
});
// Append <figure> and <img> preview node to DOM
previewWrapper.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(previewImage); // <img>
uploadImageWrapper.insertAdjacentHTML('afterbegin', removeImage); // 'x' to remove the image
// // ===== Delete figure element that wraps the image =====
// document.querySelectorAll('#show-selected-images .remove-image').forEach(i => {
// i.addEventListener('click', (e) => {
// if (e.target) {
// let deleteFigureEl = e.target.closest('figure');
// // removes the image via removing it's parent element
// deleteFigureEl.remove();
// }
// })
// })
}
* {
position: relative;
}
form {
padding: 1rem 2rem;
width: 50%;
border: 1px solid;
}
input,
button {
display: block;
margin: 2rem 0;
}
.remove-image {
background: #000;
width: 30px;
height: 30px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
top: 10px;
left: -10px;
position: absolute;
z-index: 2;
}
.img {
width: 200px;
height: 200px;
object-fit: cover;
margin: 0 1rem;
}
<form enctype="multipart/form-data" method="post" id="upload-images-form">
<input id="attach-files" type="file" name="attach-files[]" multiple>
<button name="submit" id="submit">SUBMIT</button>
<div id="show-selected-images"></div>
</form>
CodePudding user response:
The two dynamic values in the click listener are the target (which is the previewImage
itself) and the name (which is the same as the file.name
already in scope). So, all you'd really need to do is change those variable references inside a removeImage
event listener (and make removeImage
an actual element, not just a string, so that .addEventListener
can be called on it).
const removeImage = document.createElement('div');
removeImage.className = 'remove-image';
removeImage.textContent = ' X ';
removeImage.addEventListener('click', () => previewImage.click());
removeImage.addEventListener("click", () => {
const name = file.name;
[...submitData.files].forEach((file, idx) => {
if (file.name === name) {
submitData.items.remove(idx);
}
});
attachFiles.files = submitData.files;
previewImage.remove();
removeImage.remove();
});
let attachFiles = document.getElementById("attach-files");
let previewWrapper = document.getElementById("show-selected-images");
let form = document.getElementById("upload-images-form");
let submitData = new DataTransfer();
attachFiles.addEventListener("change", (e) => {
const currentSubmitData = Array.from(submitData.files);
// For each addded file, add it to submitData if not already present
[...e.target.files].forEach((file) => {
if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
submitData.items.add(file);
}
});
// Sync attachFiles FileList with submitData FileList
attachFiles.files = submitData.files;
// Clear the previewWrapper before generating new previews
previewWrapper.replaceChildren();
// Generate a preview <img> for each selected file
[...submitData.files].forEach(showFiles);
});
function showFiles(file) {
let uploadImageWrapper = document.createElement("figure");
let previewImage = new Image();
const removeImage = document.createElement('div');
removeImage.className = 'remove-image';
removeImage.textContent = ' X ';
removeImage.addEventListener('click', () => previewImage.click());
removeImage.addEventListener("click", () => {
const name = file.name;
[...submitData.files].forEach((file, idx) => {
if (file.name === name) {
submitData.items.remove(idx);
}
});
attachFiles.files = submitData.files;
previewImage.remove();
removeImage.remove();
});
// Set relevant <img> attributes
previewImage.classList.add("img");
previewImage.src = URL.createObjectURL(file);
// Append <figure> and <img> preview node to DOM
previewWrapper.append(uploadImageWrapper); // <figure> element
uploadImageWrapper.append(previewImage); // <img>
uploadImageWrapper.insertAdjacentElement('afterbegin', removeImage); // 'x' to remove the image
}
* {
position: relative;
}
form {
padding: 1rem 2rem;
width: 50%;
border: 1px solid;
}
input,
button {
display: block;
margin: 2rem 0;
}
.remove-image {
background: #000;
width: 30px;
height: 30px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
top: 10px;
left: -10px;
position: absolute;
z-index: 2;
}
.img {
width: 200px;
height: 200px;
object-fit: cover;
margin: 0 1rem;
}
<form enctype="multipart/form-data" method="post" id="upload-images-form">
<input id="attach-files" type="file" name="attach-files[]" multiple>
<button name="submit" id="submit">SUBMIT</button>
<div id="show-selected-images"></div>
</form>