Home > Software engineering >  Remove Specific File From File Array When Preview Image Is Deleted
Remove Specific File From File Array When Preview Image Is Deleted

Time:10-15

Context

  1. In the simple form shown a file input element allows for multiple file uploads.
  2. An image preview is generated for each file.
  3. 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 the submit listener and compare them to the FileList while keeping only the ones with an associated data-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>

  • Related