1

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>

7
  • 1
    For one thing, posting directly to the db from the front seems like a bad idea, you gonna expose the connection string and password in the front end bundle, I even wonder if this is possible, sql connection is not through http. I also don’t quite understand the need for adding submitter to the mix, as far as I know submitter will refer to a button that was pressed (on submit button), why would you need an info like this ? Commented Oct 8, 2022 at 12:37
  • @LukeCelitan The data is being transferred to the database with PHP in the backend. Although this is mentioned in the linked question, it wasn't intially mentioned in this question which I have now added so there aren't any security issues on that front. Commented Oct 8, 2022 at 15:45
  • @pjk_ok I agree with the first comment too. I had to read the question more than twice to understand it. just remove database part in your question altogether. DB has nothing to do with it. it's just a form submission with files. Commented Oct 9, 2022 at 3:40
  • This question makes no sense. Commented Oct 9, 2022 at 13:48
  • @sungryeol when I did that initially I immediately had an answer (subsequently deleted), and a comment (still there) saying I should never submit directly from the front end to a database for security reasons. Hence why I added that database reference in. But yes it's just a form submission with files but I can't get it to work and it's becoming hugely frustrating., Commented Oct 9, 2022 at 14:58

1 Answer 1

3
+400

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>

Sign up to request clarification or add additional context in comments.

11 Comments

Thanks for taking the time to answer. i'm travelling at the moment so will go through in more detail later. I think the dataTransfer is going to be useful because the uploader is going to have drag and drop and a styled 'select files' button that uses this too. I took all of that out to reduce the code overload in the original question
@pjk_ok if this solution works for you can you accept the answer, or provide a reason why it does not work?
Like in the codesandbox this prints a message to the console but doesn't actually submit the images, which I think I'm going to need to do with FormData(), but which means I'm pretty much where I was with the original question before editing it all down. Although a very much appreciated and useful answer I'm now just facing a different issue.
@pjk_ok I've updated the last example by simply removing the submit handler for the form, so you can submit it natively using the second approach if you like. I think you just are not understanding what I've shown, or you're not asking the question you need answered, oh well.
can you revert the changes. I do need to use dataTransfer and FormData, I just need to wrap my head around a lot of new stuff. When the bounty expires I'll mark it as correct and if I get stuck with fetch()/formData I will post a separate questoin. In fairness you did answer what I asked.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.