Context
- In the simple form shown a
file
input element allows for multiple file uploads. - An image preview is generated for each file.
- When an image is clicked on, this preview image is deleted (for simplicity I haven't included a delete button, it is deleted when you click the image).
On a side note the image files are submitted via PHP in the backend and the backend code all works as expected.
When a number of files are attached via the files input element it creates an array which you can see via the console.log([...chooseFiles.files]);
line of code, which shows details of the files attached.
Problem
Can someone explain how when an image is clicked (and thus removed/ deleted visually) how you also remove that specific image from the chooseFiles.files array?
At the moment, because the image is only visually removed, the related image file is still submitted with the form.
Codepen: https://codepen.io/thechewy/pen/xxjQwLY
let chooseFiles = document.getElementById("choose-files");
let previewWrapper = document.getElementById("preview-wrapper");
chooseFiles.addEventListener("change", (e) => {
[...chooseFiles.files].forEach(showFiles);
console.log([...chooseFiles.files]);
});
function showFiles(file) {
let previewImage = new Image();
previewImage.classList.add("img");
previewImage.src = URL.createObjectURL(file);
previewWrapper.append(previewImage); // append preview image
// -- remove the image preview visually
document.querySelectorAll(".img").forEach((i) => {
i.addEventListener("click", (e) => {
e.target.remove();
});
});
}
form {
padding: 2rem;
background: red;
width: 50%;
}
input,
button {
display: block;
margin: 1rem 0;
}
.img {
width: 200px;
height: 200px;
object-fit: cover;
margin: 0 1rem;
}
img:hover {
cursor: pointer;
}
<form enctype="multipart/form-data" method="post">
<input id="choose-files" type="file" name="choose-files[]" multiple>
<button name="submit" id="submit">SUBMIT</button>
<div id="preview-wrapper"></div>
</form>
CodePudding user response:
You can compare what is in the DOM in terms of the image previews and what is in the stored FileList
from the input
. Then only submit what is actually still in the DOM as a valid preview.
There is no way to remove an individual item from a FileList
as you can see more of an explanation here.
- Add a
data-name
attribute to the image preview. - Write a
submit
event handler for the form preventing the default behavior. - Read the
data-name
from all the.img
elements in thesubmit
listener and compare them to theFileList
while keeping only the ones with an associateddata-name
.
let chooseFiles = document.getElementById("choose-files");
let previewWrapper = document.getElementById("preview-wrapper");
let form = document.getElementById('form');
form.addEventListener('submit', (evt) => {
evt.preventDefault()
const toSend = []
const imgs = [...document.querySelectorAll('.img')].map(img => img.dataset.name);
[...chooseFiles.files].forEach(file => {
if (imgs.includes(file.name)) {
toSend.push(file)
}
})
console.log('sending', toSend);
})
chooseFiles.addEventListener("change", (e) => {
[...e.target.files].forEach(showFiles);
});
function showFiles(file) {
let previewImage = new Image();
previewImage.dataset.name = file.name;
previewImage.classList.add("img");
previewImage.src = URL.createObjectURL(file);
previewWrapper.append(previewImage); // append preview image
// -- remove the image preview visually
document.querySelectorAll(".img").forEach((i) => {
i.addEventListener("click", (e) => {
e.target.remove();
});
});
}
form {
padding: 2rem;
background: red;
width: 50%;
}
input,
button {
display: block;
margin: 1rem 0;
}
.img {
width: 200px;
height: 200px;
object-fit: cover;
margin: 0 1rem;
}
img:hover {
cursor: pointer;
}
<form enctype="multipart/form-data" method="post" id="form">
<input id="choose-files" type="file" name="choose-files[]" multiple>
<button name="submit" id="submit">SUBMIT</button>
<div id="preview-wrapper"></div>
</form>
Alternatively, you can use a DataTransfer
object to reset the FileList
from the input
each time a preview image is removed. This does a bit more work for each click to remove, but has the benefit of keeping the FileList
in sync with the list of previews. It also uses the FormData
in the submit handler so you can add other fields dynamically and post the data with fetch
.
let chooseFiles = document.getElementById("choose-files");
let previewWrapper = document.getElementById("preview-wrapper");
let form = document.getElementById('form');
form.addEventListener('submit', (e) => {
const fd = new FormData();
e.preventDefault();
for (const file of chooseFiles.files) {
fd.append('choose-files[]', file, file.name)
}
// You can POST this to the server with fetch like
// fetch(url, { method: 'POST', body: fd })
console.log('submit', Array.from(fd.values()));
});
chooseFiles.addEventListener("change", (e) => {
[...e.target.files].forEach(showFiles);
});
function showFiles(file) {
let previewImage = new Image();
previewImage.dataset.name = file.name;
previewImage.classList.add("img");
previewImage.src = URL.createObjectURL(file);
previewWrapper.append(previewImage); // append preview image
// -- remove the image preview visually
document.querySelectorAll(".img").forEach((i) => {
i.addEventListener("click", (e) => {
const transfer = new DataTransfer();
const name = e.target.dataset.name;
for (const file of chooseFiles.files) {
if (file.name !== name) {
transfer.items.add(file);
}
}
chooseFiles.files = transfer.files;
e.target.remove();
});
});
}
form {
padding: 2rem;
background: red;
width: 50%;
}
input,
button {
display: block;
margin: 1rem 0;
}
.img {
width: 200px;
height: 200px;
object-fit: cover;
margin: 0 1rem;
}
img:hover {
cursor: pointer;
}
<form enctype="multipart/form-data" method="post" id="form">
<input id="choose-files" type="file" name="choose-files[]" multiple>
<button name="submit" id="submit">SUBMIT</button>
<div id="preview-wrapper"></div>
</form>