Search code examples
javascriptevent-handlingbase64filereaderdata-uri

How does one retrieve and collect base-64 data-urls from the files-property of a multiple file input type?


I am working on a front end project where I want to store only 2 images, previousImage and nextImage, and show them on a div called previousWeek and nextWeek respectively.

I am using

// retrieve file input
document.querySelector("#input-image").addEventListener("change", function () {
  // as localstorage only supports storing strings,
  // we have to convert our image to a datURL
  const reader=new FileReader();

  reader.addEventListener("load",() => {
    // localStorage.setItem("previous-image",reader.result);
    console.log(reader.result);
  });

  for(var i=0;i<2;i++) {
    reader.readAsDataURL(this.files[i]);    
  }
  // reader.readAsDataURL(this.files[0]);
  // console.log(this.files);
});

document.addEventListener("DOMContentLoaded", () => {
  const recentImageDataURL = localStorage.getItem("recent-image");
  if (recentImageDataURL) {
    document.querySelector("#previousImage").setAttribute("src", recentImageDataURL);
  }
});        

to take input and show it previousImage div

How can I do the same, but store the next input in local storage as 'next-image'?

What I've tried

Using another readAsDataUrl for the next input in the this.files using

reader.readAsDataURL(this.files[1]);

then setting item in localstorage using

localStorage.setItem("next-image",reader.result);

That gives me the error

script.js:30 Uncaught TypeError: Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'.

TLDR: I want to store only 2 images in local storage, and i only have 1 input html element

<input type='file' accept='image/*' id="input-image">

from which I'm reading both images


Solution

  • The OP's problem is due to the FileReader instance's method readAsDataURL.

    This process is blocking and can not be used on a reader instance by continuously plainly invoking it without triggering errors.

    Thus one either looks for a promised based solution or as for the OP's event based approach one turns the reader's load handler into a function which manages both the continuous collection of base 64 image sources and its final local storage once all image data has been captured.

    function storeBase64ImageSourcesLocally([ recent, next ]) {
      const storageData = JSON
        .stringify({ recent, next });
    
      console.log({ recent, next, storageData });
    
      // localStorage
      //   .setItem('base-64-image-sources', storageData);
    }
    
    function proceedWithReadFileAsDataURL(reader, files) {
      if (files.length >= 1) {
        reader
          .readAsDataURL(files.shift());
      }
    } 
    
    function collectBase64ImageSourceFromBoundData() {
      const { reader, sources: { files, images } } = this;
      const base64ImageSource = reader.result;
    
      images
        .push(base64ImageSource);
    
      console.log({ images });
    
      if (images.length >= 2) {
        storeBase64ImageSourcesLocally(images);
      }
    
      // continue with the `readAsDataURL` process
      // which is blocking and can not just be invoked
      // continuously on `reader` one after the other.
      proceedWithReadFileAsDataURL(reader, files);
    }
    function collectAndStoreBase64ImageData({ currentTarget }) {
      let { files } = currentTarget;
    
      // just in case of minimum two selected files.
      if (files?.length >= 2) {
    
        files = [...files]
          // limit the iterable files to just 2.
          .slice(0, 2);
    
        const reader = new FileReader();
        reader
          .addEventListener(
            'load',
            collectBase64ImageSourceFromBoundData
              .bind({ reader, sources: { files, images: [] } }),
          );
    
        // initially trigger the `readAsDataURL` process
        // which is blocking and can not just be invoked
        // continuously on `reader` one after the other.
        proceedWithReadFileAsDataURL(reader, files);
      }
    }
    
    document
      .querySelector('#input-image')
      .addEventListener('change', collectAndStoreBase64ImageData);
    body { margin: 0; }
    label > span { display: block; margin: 0 0 6px 0; }
    .as-console-wrapper { max-height: 70%!important; top: auto; }
    <label>
      <span>Feel encouraged to select at least 2 image files ...</span>
    
      <input type="file" multiple accept="image/*" id="input-image" />
    </label>