2

I have a form that takes file uploads and it currently has a limit of 10 files per upload. There are PHP validations in the backend for this too.

When more than 10 files are attached, I currently have a JavaScript slice(0, 10) method inside a change event for the file input element, which removes any files (and their preview image thumbnails) when the number attached is more than 10 files.

// For each added file, add it to submitData (the DataTransfer Object), if not already present
[...e.target.files].slice(0,10).forEach((file) => {
    if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
        submitData.items.add(file);
    }
});

The Issue

What I can’t seem to do though is work out a way to slice() the files array in a compound attachment situation, i.e. if 8 files are attached initially, and then the user decides to add another 4 prior to submitting the form, taking the total to 12. The current slice only happens when more than 10 are added in one go.

I have a decode() method that runs inside a loop (for every image attached) that carries out frontend validations, and a promiseAllSettled() method that waits for the last image to be attached prior to outputting a main error message telling the user to check the specific errors on the page.

Question

How do I slice the array on the total number of files appended, if the user has initially attached a file count less than 10, then attaches further files taking it more than 10 prior to form submission?

const attachFiles = document.getElementById('attach-files'), // file input element
dropZone = document.getElementById('dropzone'),

submitData = new DataTransfer();

dropZone.addEventListener('click', () => {
    // assigns the dropzone to the hidden 'files' input element/file picker
    attachFiles.click();
});

attachFiles.addEventListener('change', (e) => {

    const currentSubmitData = Array.from(submitData.files);

    console.log(e.target.files.length);

    // For each added file, add it to 'submitData' if not already present (maximum of 10 files with slice(0, 10)
    [...e.target.files].slice(0,10).forEach((file) => {
        if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
            submitData.items.add(file);
        }
    });

    // Sync attachFiles FileList with submitData FileList
    attachFiles.files = submitData.files;

    // Clear the previewWrapper before generating new previews
    previewWrapper.replaceChildren();

    // the 'decode()' function inside the 'showFiles()' function is returned
    // we wait for all of the promises for each image to settle 
    Promise.allSettled([...submitData.files].map(showFiles)).then((results) => {

        // output main error message at top of page alerting user to error messages attached to images

    });
    
}); // end of 'change' event listener
    
function showFiles(file) {

    // code to generate image previews and append them to the 'previewWrapper'

    // then use the decode() method that returns a promise and do JS validations on the preview images
    return  previewImage.decode().then(() => {
    
        // preform JS validations and append
    
        }).catch((error) => {
            console.log(error)
        });

} // end of showfiles(file)
paulo77
  • 174
  • 14

4 Answers4

5

Instead of looking into your whole code, Here I came up with the solution/Suggestion as per looking into a specific piece of code.

Once you spliced the initial set of files, After that you can check how many files are remaining and then you can pass the second parameter in the splice dynamically based on the required number of files.

Steps :

  • Create a separate empty array and insert first selection of files into that array on change event.

  • Now check for this newly created array length, if there is any number of elements then you can update the 2nd splice method parameter like this :

    secondParameter = secondParameter - newArr.length
    

Live Demo :

let splicedArrCount = 10;
let resArr = [];

function spliceArr() {
  const inputVal = Number(document.getElementById('number').value);
  if (inputVal) {
    const arr = Array(inputVal).fill(inputVal);
    if (!resArr.length) {
      resArr = [...arr];
    } else if (resArr.length) {
        splicedArrCount = splicedArrCount - resArr.length
      resArr.push(...arr.splice(0, splicedArrCount));
    }
  }
  console.log(resArr);
}
<input type="number" id="number" onChange="spliceArr()"/>

In the above demo, You can do a testing by inserting a number into a input box, which will convert the number into an array elements. For ex. If you will enter 8, it will create an array with 8 elements and then if you pass 4, it will update an array. i.e. [8, 8, 8, 8, 8, 8, 8, 8, 4, 4]

Debug Diva
  • 26,058
  • 13
  • 70
  • 123
1

We can keep the count of the number of files already added. Instead of [...e.target.files].slice(0,10).forEach((file) => {}, we can do something like:

var numberOfFilesToAdd = 10 - currentSubmitData.length;
[...e.target.files].slice(0,numberOfFilesToAdd).forEach((file) => {}
Taleeb
  • 1,888
  • 18
  • 25
1

Keep an array (below, called allSelectedFiles) of all of the files selected so far, and keep adding to that array as user selects more.

Keep another array (below, called filesForUpload) that's a subset of the first array, filtered for uniqueness and sliced to the max length. Present this subset array in the DOM to give user feedback, and use it to drive the actual upload on submit.

let allSelectedFiles = [];
let filesForUpload = [];

const totalAllowed = 4;  // 10 for the OP, but 3 is simpler to demo

const attachFiles = document.getElementById('attach-files');

attachFiles.addEventListener('change', e => {
  allSelectedFiles = [... allSelectedFiles, ...e.target.files];
  
  let filenames = new Set();
  filesForUpload = allSelectedFiles.filter(f => {
    let has = filenames.has(f.name);
    filenames.add(f.name);
    return !has;
  });
  filesForUpload = filesForUpload.slice(0, totalAllowed);
  showFiles(filesForUpload);
});

// unlike the OP, just to demo: fill a <ul> with filename <li>'s
function showFiles(array) {
  let list = document.getElementById('file-list');
  while(list.firstChild) {
    list.removeChild( list.firstChild );
  }
  for (let file of array) {
    let item = document.createElement('li');
    item.appendChild(document.createTextNode(file.name));
    list.appendChild(item);
  }
}

// on form submit, not called in the above, trim the selected files 
// using the same approach: someSelectedFiles()
function submit() {
  /* 
  let submitData = new DataTransfer();
  for (let file of filesForUpload) {
    submitData.add(file)
  }
  // and so on
  */
}
<h3>Input some files</h3>
<p>To see the logic, choose > 4 files, choose some overlapping names</p>
<input type="file" id="attach-files" multiple />
<ul id="file-list"></ul>
danh
  • 62,181
  • 10
  • 95
  • 136
1

Slicing before checking duplicates can possibly drop valid values.

I would go for something like this:

for (const file of e.target.files) {
  if (currentSubmitData.every((currFile) => currFile.name !== file.name)) {
    submitData.items.add(file);
  }
  if (submitData.items.size >= 10) break; // assuming submitData.items is a Set
}
Benoît Vidis
  • 3,908
  • 2
  • 23
  • 24