Context
- In the simple form shown a
fileinput 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-nameattribute to the image preview. - Write a
submitevent handler for the form preventing the default behavior. - Read the
data-namefrom all the.imgelements in thesubmitlistener and compare them to theFileListwhile 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>
