skip to Main Content

Hi I am trying to build a similar functionality to the Dropzone.js library. My problem is that even it displays the correct number of thumbnails when i submit my form it submits only the last selected items. For example in my code try to select firstly 3 images and then 3 more. It will display 6 thumbnails but in the input files its says: 3 files selected. Is there any way to make it so the input will store all files instead of the last selected?

Here is my code:

document.getElementById("authorGallery").addEventListener("change", function(event) {
  var fileZone = document.querySelector(".filezone");
  
  // Retrieve the previously selected files, if any
  var selectedFiles = Array.from(fileZone.querySelectorAll(".author-gallery-image"));

  var files = event.target.files;
  for (var i = 0; i < files.length; i++) {
    var file = files[i];

    var fileItem = document.createElement("div");
    fileItem.classList.add("author-gallery-image");

    var thumbnail = document.createElement("img");
    thumbnail.classList.add("thumbnail");

    (function(fileItem, thumbnail) {
      var reader = new FileReader();
      reader.onload = function(e) {
        var img = new Image();
        img.onload = function() {
          var canvas = document.createElement("canvas");
          var ctx = canvas.getContext("2d");

          canvas.width = 800;
          canvas.height = 800;

          var width = Math.min(img.width, 800);
          var height = Math.min(img.height, 800);
          var x = (img.width - width) / 2;
          var y = (img.height - height) / 2;

          ctx.drawImage(img, x, y, width, height, 0, 0, 800, 800);

          thumbnail.src = canvas.toDataURL("image/jpeg");

          canvas = null;
        };
        img.src = e.target.result;
      };
      reader.readAsDataURL(file);
    })(fileItem, thumbnail);

    fileItem.appendChild(thumbnail);
    fileZone.appendChild(fileItem);

    // Add the newly selected file to the array
    selectedFiles.push(fileItem);
  }

  
  // Append the previously selected files back to the file zone
  for (var j = 0; j < selectedFiles.length; j++) {
    fileZone.appendChild(selectedFiles[j]);
  }
});
.filezone {
  display: flex;
  flex-wrap: wrap;
}

.filezone .author-gallery-image {
  flex-basis: 20%; /* Set the width of each item to occupy 20% of the container */
  box-sizing: border-box; /* Include padding and border in the item's width */
  padding: 5px; /* Add some padding around each item */
}

.author-gallery-image img {
width: 100%;
height: auto;
border-radius: 20px;
object-fit: cover;
border: 3px solid transparent;
cursor:pointer;
transition:all ease-in-out 0.2s; 
}
<div id="galleryImages" class="filezone">
    
</div>





<div id="galleryFile">
<input type="file" id="authorGallery" name="authorgallery[]" multiple>
</div>

2

Answers


  1. Just hide the file input, click it with a button and display number of images yourself. After the selection don’t forget to null the file input’s value so if you add deletion of images the user could select the same file again, otherwise the change event won’t happen.

    For saving the images collect them in an array and then use FormData and fetch() to POST your images to a needed endpoint.
    I didn’t manage to POST it purely with the <form> though… I guess you should listen for formdata event and add to the form’s formdata the image files.

    But better would be to upload images with an upload progress using XMLHttpRequest or a library like axios. Unfortunately fetch() doesn’t support the upload progress (weird to have such a situation with a newer API). Uploading without a progress is not the best UX imho…

    enter image description here

    const $authorGallery = document.getElementById("authorGallery");
    document.querySelector('#add').addEventListener('click', () => $authorGallery.click());
    document.querySelector('form').addEventListener('submit', async e => {
    
      e.preventDefault();
    
      const body = new FormData(e.target);
      images.forEach(image => body.append('authorgallery[]', image));
     
      await fetch('/save-gallery.php', {method: 'POST', body});
    
      // display some success message
     
    });
    
    const images = [];
    
    $authorGallery.addEventListener("change", function(event) {
      
      var fileZone = document.querySelector(".filezone");
      
      // Retrieve the previously selected files, if any
      var selectedFiles = Array.from(fileZone.querySelectorAll(".author-gallery-image"));
    
      var files = event.target.files;
      
      for (var i = 0; i < files.length; i++) {
        var file = files[i];
        images.push(file);
    
        var fileItem = document.createElement("div");
        fileItem.classList.add("author-gallery-image");
    
        var thumbnail = document.createElement("img");
        thumbnail.classList.add("thumbnail");
    
        (function(fileItem, thumbnail) {
          var reader = new FileReader();
          reader.onload = function(e) {
            var img = new Image();
            img.onload = function() {
              var canvas = document.createElement("canvas");
              var ctx = canvas.getContext("2d");
    
              canvas.width = 800;
              canvas.height = 800;
    
              var width = Math.min(img.width, 800);
              var height = Math.min(img.height, 800);
              var x = (img.width - width) / 2;
              var y = (img.height - height) / 2;
    
              ctx.drawImage(img, x, y, width, height, 0, 0, 800, 800);
    
              thumbnail.src = canvas.toDataURL("image/jpeg");
    
              canvas = null;
            };
            img.src = e.target.result;
          };
          reader.readAsDataURL(file);
        })(fileItem, thumbnail);
    
        fileItem.appendChild(thumbnail);
        fileZone.appendChild(fileItem);
    
        // Add the newly selected file to the array
        selectedFiles.push(fileItem);
      }
    
      
      // Append the previously selected files back to the file zone
      for (var j = 0; j < selectedFiles.length; j++) {
        fileZone.appendChild(selectedFiles[j]);
      }
      
      document.querySelector('#count').textContent = selectedFiles.length + ' image(s) selected';
      $authorGallery.value = null;
    });
    .filezone {
      display: flex;
      flex-wrap: wrap;
    }
    
    .filezone .author-gallery-image {
      flex-basis: 20%; /* Set the width of each item to occupy 20% of the container */
      box-sizing: border-box; /* Include padding and border in the item's width */
      padding: 5px; /* Add some padding around each item */
    }
    
    .author-gallery-image img {
    width: 100%;
    height: auto;
    border-radius: 20px;
    object-fit: cover;
    border: 3px solid transparent;
    cursor:pointer;
    transition:all ease-in-out 0.2s; 
    }
    <form>
      <div id="galleryImages" class="filezone">
    
      </div>
      <div id="galleryFile">
        <input style="display:none" type="file" id="authorGallery" multiple>
        <button type="button" id="add">Add images</button>
        <div id="count"></div>
      </div>
      <button>Save</button>
    </form>
    Login or Signup to reply.
  2. Every time you select something from the file input they gets cleared. (as you have notice)

    To solve this you could store all files you have selected in some variable and later update the FileList input.files. But do note that it ins’t possible to simply modify/update it. You need to create a FileList in a round about way…

    I toke the liberty of modernizing your code a bit with const, let, createImageBitmap to reduce some noice around having to create function closures.

    const/let are scoped in {} (unlike var that gets hoisted to the top

    const authorGallery = document.getElementById('authorGallery')
    const fileZone = document.querySelector('.filezone')
    const allFiles = [] // To keep track of all the files added
    
    /** @params {File[]} files - Array of files to be included in the FileList */
    function fileListFrom (files) {
      const b = new ClipboardEvent("").clipboardData || new DataTransfer()
      for (const file of files) b.items.add(file)
      return b.files
    }
    
    authorGallery.addEventListener('change', event => {
      /** @type {File[]} */
      const files = event.target.files
    
      for (const file of files) {
        // Store added file
        allFiles.push(file)
    
        const fileItem = document.createElement('div')
        fileItem.classList.add('author-gallery-image')
    
        const thumbnail = new Image()
        thumbnail.classList.add('thumbnail')
    
        createImageBitmap(file).then(async bitmap => {
          const canvas = document.createElement('canvas')
          const ctx = canvas.getContext('2d')
                
          canvas.width = canvas.height = 800
          
          const width = Math.min(bitmap.width, 800)
          const height = Math.min(bitmap.height, 800)
          const x = (bitmap.width - width) / 2
          const y = (bitmap.height - height) / 2
    
          ctx.drawImage(bitmap, x, y, width, height, 0, 0, 800, 800)
    
          thumbnail.src = canvas.toDataURL()
        })
    
        fileItem.appendChild(thumbnail)
        fileZone.appendChild(fileItem)
      }
    
      // Update the FileList with the new and the old items
      authorGallery.files = fileListFrom(allFiles)
    })
    .filezone{display:flex;flex-wrap:wrap}.filezone .author-gallery-image{flex-basis:20%;box-sizing:border-box;padding:5px}.author-gallery-image img{width:100%;height:auto;border-radius:20px;object-fit:cover;border:3px solid transparent;cursor:pointer;transition:all ease-in-out 0.2s}
    <div id="galleryImages" class="filezone"></div>
    
    <div id="galleryFile">
      <input type="file" id="authorGallery" name="authorgallery[]" multiple>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search