skip to Main Content

I’m making an app where users can upload multiple photos.
When they open <input type="file" multiple>, I then listen on change event, create a faux array of those files and dynamically create images for the user to preview them before uploading.

    fauxArray = Array.from(e.target.files);
    for (image of fauxArray) {
      let newImage = document.createElement("IMG");
      newImage.src = URL.createObjectURL(image);
    }

I do this so the user can decide not to upload all of them and maybe remove some before the actual saving on the server. So I have a button next to each image, and if they use it, I alter the fauxArray accordingly.

The problem happens when I try to add this to fetch request.

const formData     = new FormData();

formData.append( 'memoryPhotos', fauxArray ); // <- HERE
formData.append( 'memoryTitle', packageTitle );
formData.append( 'memoryDescription', packageDescription );

fetch("gui/save.php", {
  method: "post",
   body: formData,
})

Because on PHP side, when I read it with $_POST['memoryPhotos'], I just get the string [object File],[object File] (if there were 2 files etc.).

How to attach my array of files to formdata to be read on server? Or am I using a wrong format for the duplicated images from input?

2

Answers


  1. You should read the photo’s data and serialize them for example as data urls. To post a JS object you need to serialize it to JSON and use json_decode() on the php side. You can use await Promise.all without wrapping in an async function in JS modules.

    const formData     = new FormData();
    
    const readFile = async (file) => new Promise((resolve, reject) => {
     
      const reader = new FileReader();
      reader.onerror = reject;
      reader.onload = resolve(reader.result);
      reader.readAsDataURL(file);
     
    });
    
    (async () => {
    
      const photos = await Promise.all(fauxArray.map(async (file) => {
        return {
          name: file.name,
          dataURL: await readFile(file);
        };
      }));
    
      formData.append( 'memoryPhotos', JSON.stringify(photos)); // <- HERE
      formData.append( 'memoryTitle', packageTitle );
      formData.append( 'memoryDescription', packageDescription );
    
      fetch("gui/save.php", {
        method: "post",
         body: formData,
      });
    
    })();

    But better attach the files to the FormData directly:

    https://developer.mozilla.org/en-US/docs/Web/API/FormData/append

    const formData = new FormData();
    
    (async () => {
    
      fauxFiles.forEach(file => formData.append( 'memoryPhotos[]', file ));
    
      formData.append( 'memoryTitle', packageTitle );
      formData.append( 'memoryDescription', packageDescription );
    
      fetch("gui/save.php", {
        method: "post",
         body: formData,
      });
    
    })();
    Login or Signup to reply.
  2. Based upon your description of the app and the snippets of code shown perhaps the following will be of interest. The above fails as you simply assign the entire "fauxArray" directly as a value to the FormData object rather than the individual files themselves contained within that array. The solution is to iterate through that array and add each item as the value of an array-like field – below however to ease the deletion of files prior to upload the "fauxArray" is declared as an Object literal "faux" – so that one can use delete on the object rather than array slice / splice etc

    <?php
        if( $_SERVER['REQUEST_METHOD']=='POST' && isset( 
            $_FILES['memoryPhotos'],
            $_POST['memoryTitle'],
            $_POST['memoryDescription']
        )){
            ob_clean();
            
            $files=(object)$_FILES['memoryPhotos'];
            $data=array();
                
            foreach( $files->name as $i => $void ){
                $name = $files->name[$i];
                $size = $files->size[$i];
                $type = $files->type[$i];
                $tmp  = $files->tmp_name[$i];
                $error= $files->error[$i];
                
                // do whatever processing...
                $data[]=array('name'=>$name,'size'=>$size);
            }
            
            exit(json_encode($data));
        }
    ?>
    <!DOCTYPE html>
    <html lang='en'>
        <head>
            <meta charset='utf-8' />
            <title></title>
            <style>
                output > div{margin:0.25rem;display:inline-block;padding:0.25rem;border:1px solid grey}
                output > div > img{width:150px;clear:none;}
                output > div > input{float:right;margin:0.5rem;color:red}
            </style>
        </head>
        <body>
        
            <form name='memories' method='post'>
                <label>Pictures: <input type='file' name='memoryPhotos' multiple /></label>
                <label>Title: <input type='text' name='memoryTitle' value='some random nonsense that makes the title' /></label>
                <label>Description: <input type='text' name='memoryDescription' value='further random nonsense that describes this thingy-ma-jig' /></label>
                <input type='button' name='bttnsave' value='Go' />
            </form>
            
            <output></output>
            
            
            <script>
                // user editable object that stores references to files selected
                let faux={};
                
                
                const d=document;
                const q=(e,n=d)=>n.querySelector(e);
                const form=d.forms.memories;
                const output=q('output');
                
                // utility to quickly generate simple dom elements
                const create=(t='div',a={},p=false)=>{
                    let el=d.createElement(t);
                    Object.keys(a).forEach(p=>el.setAttribute(p,a[p]))
                    if( p ) p.appendChild(el);
                    return el;
                };
                
                // fetch intermediary callback
                const getresponse=(r)=>typeof(r)=='object' && Object.keys(r).length > 0 ? r.json() : r.text();
                
                // callback to react to file selection changes and display thumbnails of images.
                // each image has a button associated that will delete the image and modify global "faux" object
                const changehandler=(e)=>{
                    output.innerHTML='';
                    
                    Array.from( e.target.files ).forEach( ( file, index )=>{
                        let div=create( 'div',{},output );
                        
                        let img=new Image();
                            img.onload=()=>div.appendChild( img );
                            img.src=URL.createObjectURL( file );
                        
                        let bttn=create( 'input',{ type:'button',value:'Delete','data-id':index },div )
                            bttn.addEventListener('click',deletehandler);
                        // populate the Object literal
                        faux[ index ]=file;
                    });
                };
                
                // delete the file from the global "faux" object
                const deletehandler=(e)=>{
                    delete faux[ e.target.dataset.id ];
                    output.removeChild( e.target.closest('div') );
                };
                
                // do something interesting after uploading images via "Fetch"
                const uploadcallback=(r)=>{
                    faux={}; // reset the "faux" object
                    console.log( r );
                    output.innerHTML='Look over there! Interesting things are happening...';
                };
                
                // error handler
                const errorhandler=(e)=>{
                    console.warn(e);
                    output.textContent=e;
                    return false;
                };
                
                // invoke "Fetch" to upload images and memory text
                const clickhandler=(e)=>{
                    if( Object.keys( faux ).length > 0 ){
                        let fd=new FormData();
                            fd.set('memoryTitle',form.memoryTitle.value);
                            fd.set('memoryDescription',form.memoryDescription.value);
                        
                        // Add each file to the FormData within an array like key/value field
                        Object.values( faux ).forEach( file=>{
                            fd.append('memoryPhotos[]',file);
                        });
                        
                        fetch( location.href, { method:'post',body:fd } )
                            .then( getresponse )
                            .then( uploadcallback )
                            .catch( errorhandler )
                        
                        return true;
                    }
                    return errorhandler('no files selected')
                };
                
                // bind the listeners...
                form.memoryPhotos.addEventListener('change',changehandler);
                form.bttnsave.addEventListener('click',clickhandler);
            </script>
        </body>
    </html>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search