Search code examples
javascriptc#jqueryasp.net-mvcasp.net-mvc-5

FileReader onload and assign value in loop?


i'm having difficulties on getting file reader content assignment. Is there anyway to wait the file reader finish onload and assign the file content before it push to the array?

I have a list of input file type button as below:

@for (int i = 0; i < @Model.LetterList.Count(); i++)
{
  <tr>
     <td>
         <input type="file" id="LetterAttachment" name="LetterAttachment" accept="application/pdf">
     </td>
  </tr>
}

enter image description here

When i click submit, i want to assign the file content value into my form list in loop, below is my javascript code :

var attach=""; // empty variable for assign file content
function ApproverAction(action) {

        var formList = [];

        $("input[name='LetterAttachment']").each(function () {

            if (this.files && this.files[0]) {

                // I perform file reader here to assign the file content into attach....
                var FR = new FileReader();
                FR.onload = function (e) {
                    attach = e.target.result;
                }    
                FR.readAsDataURL(this.files[0]);

                 var form = {
                        ID: newGuid(),
                        FileContents: attach, <<< ---- However it showing empty 
                        DocumentName: this.files[0].name,
                        DocumentSize: this.files[0].size,
                        DocumentContentType: 'application/pdf',
                        SourceType: 'OnlineAssessment',
                        CreatedDate: '@DateTime.Now'
                }

                formList.push(form);
            }

        });

        console.log(formList);
}

However i can't get the result quite correctly for output :

enter image description here

Any help and tips is highly appreciated! Thanks!


Solution

  • Use a promise for each file that resolves in the onload function and push those promises into an array

    Then use Promise.all() to send the data once all promises have resolved. Note that the error handling will need to be improved depending on process flow you want

    function ApproverAction(action) {
    
      var filePromises = [];
    
      $("input[name='LetterAttachment']").each(function() {
    
          if (this.files && this.files[0]) {
    
             // reference to this to use inside onload function
             var _input = this;
    
            var promise = new Promise(function(resolve, reject) {
    
                var FR = new FileReader();               
    
                FR.onload = function(e) {    
                  var form = {
                    ID: newGuid(),
                    FileContents: e.target.result;,
                    DocumentName: _input.files[0].name,
                    DocumentSize: _input.files[0].size,
                    DocumentContentType: 'application/pdf',
                    SourceType: 'OnlineAssessment',
                    CreatedDate: '@DateTime.Now'
                  }    
    
                  // resolve promise with object
                  resolve(form);
                }
                FR.readAsDataURL(this.files[0]);
    
                if(FB.error){
                  //   needs more robust error handling, for now just reject promise
                  reject(FB.error)
                }
                // push promise to array
                filePromises.push(promise)
              }
    
            });         
        }
    
      });
      // return a new promise with all the data
      return Promise.all(filePromises)
         
    }
    

    Usage with promise returned from function

    ApproverAction(action).then(function(formList){
          // do something with the data array
          console.log(formList);         
          
    }).catch(function(err){
          console.error("Ooops something went wrong')
    });