Search code examples
javascripthtmlformsdom

Failure to delete image from input with JavaScript


I have this code below HTML and JavaScript. There is a PHP code that is working successfully sending everything to the database, which does not need to be shown.

<form action="" method="post" enctype="multipart/form-data">
    <label for="titulo">Título:</label>
    <input type="text" id="titulo" name="titulo" required><br><br>

    <label for="descricao">Descrição:</label><br>
    <textarea id="descricao" name="descricao" required></textarea><br><br>
    <div id="preview"></div>
    <label for="imagens">Imagens:</label>
    <input type="file" id="imagens" name="imagens[]" multiple accept="image/*">
    <small>(Você pode selecionar várias imagens segurando a tecla Ctrl)</small><br><br>

    <input type="submit" value="Cadastrar Anúncio">
</form>

<script>
    (function() {
        function previewImages() {
            var input = document.getElementById("imagens");
            var preview = document.getElementById("preview");
            preview.innerHTML = ""; // Limpa a prévia de imagens anterior

            if (input.files && input.files.length > 0) {
                for (var i = 0; i < input.files.length; i++) {
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        var imageContainer = document.createElement("div");
                        imageContainer.className = "image-container";

                        var image = document.createElement("img");
                        image.src = e.target.result;
                        image.style.maxWidth = "100px"; // Define a largura máxima das miniaturas
                        imageContainer.appendChild(image);

                        var deleteButton = document.createElement("button");
                        deleteButton.innerText = "Deletar";
                        deleteButton.className = "delete-button";
                        deleteButton.addEventListener("click", function() {
                            // Função para deletar a imagem
                            imageContainer.parentNode.removeChild(imageContainer);
                            // Desanexar a imagem do input file
                            //input.value = "";
                        });
                        imageContainer.appendChild(deleteButton);

                        preview.appendChild(imageContainer);
                    };
                    reader.readAsDataURL(input.files[i]);
                }
            }
        }

        // Adicione o evento onchange ao campo de entrada de arquivo fora das tags HTML
        var imagensInput = document.getElementById("imagens");
        imagensInput.onchange = previewImages;
    })();
</script>

The problem is based on the fact that when I click the 'Delete' button that appears underneath the image thumbnail, it actually deletes the thumbnail and appears to delete the temporary file, I assume.

Here is the isolated part of the code already shown, responsible for deleting the image thumbnail.

var deleteButton = document.createElement("button");
deleteButton.innerText = "Deletar";
deleteButton.className = "delete-button";
deleteButton.addEventListener("click", function() {
    // Função para deletar a imagem
    imageContainer.parentNode.removeChild(imageContainer);
    // Desanexar a imagem do input file
    //input.value = "";
});
imageContainer.appendChild(deleteButton);

preview.appendChild(imageContainer);

But, all images attached to the input (made for multiple files) are sent to the database, including the image that was deleted by the button.

The right thing to do would be to preserve the image that was deleted by the button and prevent it from being sent to the database.

I've read answers in other threads that gave this solution:

reader.onload = function(e) {
//rest of code omitted
      input.value = "";
});

But this doesn't work, because when you insert this line of code, all images are deleted, instead of the only one chosen.

How can I solve it?


Solution

  • This is a long example, but my idea is to maintain an array (here a Map object) of the files selected. Files can be added (a file picker for adding one or more files) or removed (your delete buttons) from that array. In the end (on the submit event) you can create a formData object and add the values from all the form fields AND the files in the array. Let JavaScript handle the submit event with a fetch request with the formData object as body.

    var images = new Map();
    const preview = document.getElementById('preview');
    
    document.forms.form01.addEventListener('submit', e => {
      e.preventDefault();
      let data = new FormData(e.target);
      data.delete('images');
      images.forEach((file, id) => {
        let blob = dataURItoBlob(file.src);
        data.append('images[]', blob, file.name);
      });
      fetch('post.php', {
        method: 'POST',
        body: data
      });
    });
    
    document.forms.form01.images.addEventListener('input', e => {
      let input = e.target;
      [...input.files].forEach(file => {
        let reader = new FileReader();
        reader.fileinfo = {
          name: file.name,
          lastModified: file.lastModified,
          size: file.size,
          type: file.type
        };
        reader.addEventListener('load', e => {
          let file = e.target.fileinfo;
          file.src = e.target.result;
          images.set(`id${file.lastModified}${file.size}`, file);
          previewImages();
        });
        reader.readAsDataURL(file);
      });
      input.value = "";
    });
    
    document.forms.form01.addEventListener('click', e => {
      switch (e.target.name) {
        case 'delete':
          images.delete(e.target.value);
          previewImages();
          break;
        case 'browse':
          e.target.form.images.click();
          break;
      }
    });
    
    function previewImages() {
      // add images to the preview div if not already there
      images.forEach((file, id) => {
        if (!preview.querySelector(`#${id}`)) {
          let imageContainer = document.createElement("div");
          imageContainer.className = "image-container";
          imageContainer.id = id;
    
          let image = document.createElement("img");
          image.src = file.src;
          image.style.maxWidth = "100px"; // Define a largura máxima das miniaturas
          imageContainer.appendChild(image);
    
          var deleteButton = document.createElement("button");
          deleteButton.innerText = "Deletar";
          deleteButton.name = "delete";
          deleteButton.type = "button";
          deleteButton.value = id;
          imageContainer.appendChild(deleteButton);
          preview.appendChild(imageContainer);
        }
      });
      // remove images from preview if not in the Map object
      preview.querySelectorAll('.image-container').forEach(div => {
        if (!images.has(div.id)) {
          div.remove();
        }
      });
    }
    
    function dataURItoBlob(dataURI) {
      let byteString = atob(dataURI.split(',')[1]);
      let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
      let ab = new ArrayBuffer(byteString.length);
      let ia = new Uint8Array(ab);
      for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }
      return new Blob([ab], {
        type: mimeString
      });
    }
    <form name="form01" action="" method="post" enctype="multipart/form-data">
      <label for="titulo">Título:</label>
      <input type="text" id="titulo" name="titulo" required><br><br>
    
      <label for="descricao">Descrição:</label><br>
      <textarea id="descricao" name="descricao" required></textarea><br><br>
      <div id="preview"></div>
      <label>Imagens: <button type="button" name="browse">Browse…</button>
        <input type="file" name="images" multiple accept="image/*" style="display:none">
      </label>
      <small>(Você pode selecionar várias imagens segurando a tecla Ctrl)</small><br><br>
    
      <input type="submit" value="Cadastrar Anúncio">
    </form>