Thursday, August 30, 2018

FileReader with multi Ajax file upload and progress

Leave a Comment

I have a multi-file input field:

<input type="file" class="image_file" multiple>

I am using FileReader to show previews of images whilst they are being uploaded.

I now also want to show a progress bar on each individual image whilst it is being uploaded. Here is what I have tried:

$('.image_file').change(function() {     var input = $(this);     var files = this.files;     var total = files.length;     var url = input.attr('data-url');      for (var i = 0; i < total; i++) {         var formData = new FormData();         var file = files[i];          formData.append('image_file', file);          var reader = new FileReader();          reader.onload = function(e) {             var container = $('.photos .photo:not(.active):first');              if (container.length) {                 container.css('background-image', 'url(' + e.target.result + ')').addClass('active uploading');             }         };          reader.readAsDataURL(file);          $.ajax({             type: 'post',             url: url,             data: formData,             cache: false,             processData: false,             contentType: false,             xhr: function() {                 var myXhr = $.ajaxSettings.xhr();                  var progressElem = container.find('progress');                  if (myXhr.upload) {                     myXhr.upload.addEventListener('progress', function(e) {                         if (e.lengthComputable) {                             progressElem.attr({                                 value: e.loaded,                                 max: e.total                             });                         }                     }, false);                 }                  return myXhr;             },             success: function(result) {                 if (result.status == true) {                     $('.success-message').show();                 }                 else {                     alert('There was an error uploading your file.);                 }             }         });     } }); 

The issue I am having is on this line in the xhr function:

var progressElem = container.find('progress');

The image preview appears but the AJAX upload isn't working. No errors are shown in the console either. I think because var container was set within the reader.onload function, the xhr function doesn't have access to it.

If I move that var outside of the function, the image upload works but only one image preview and one progress bar is shown.

Does anybody know the correct way to do this?

2 Answers

Answers 1

The problem is that there is a single xhr that is created and deleted when the for loop runs. The previous xhr are destroyed once the code finishes so it will never run.

The way I got round this was to not use jQuery and/or create a new xmlhttprequest for each for loop.

var array = []; //ADDED HERE  $('.image_file').change(function() {     var input = $(this);     var files = this.files;     var total = files.length;     var url = input.attr('data-url');      for (var i = 0; i < total; i++) {         var formData = new FormData();         var file = files[i];          formData.append('image_file', file);          var reader = new FileReader();          reader.onload = function(e) {             var container = $('.photos .photo:not(.active):first');              if (container.length) {                 container.css('background-image', 'url(' + e.target.result + ')').addClass('active uploading');             }         };          reader.readAsDataURL(file);          array[array.Length] = $.ajax({ //ADDED HERE             type: 'post',             url: url,             data: formData,             cache: false,             processData: false,             contentType: false,             xhr: function() {                 var myXhr = $.ajaxSettings.xhr();                  var progressElem = container.find('progress');                  if (myXhr.upload) {                     myXhr.upload.addEventListener('progress', function(e) {                         if (e.lengthComputable) {                             progressElem.attr({                                 value: e.loaded,                                 max: e.total                             });                         }                     }, false);                 }                  return myXhr;             },             success: function(result) {                 if (result.status == true) {                     $('.success-message').show();                 } else {                     alert('There was an error uploading your file.);                 }             }         });     } }); 

I need to emphasis that I haven't looked through your code completely but hopefully this will steer you in the right direction.

Answers 2

Looking at your question description, I assume:

  1. Image Preview works since you mentioned "The image preview appears"
  2. Image uploads since you mentioned "If I move that var outside of the function, the image upload works..."

Where is the problem then?

The problem is your variable container is not accessible inside xhr() function as you mentioned already.

What is the solution?

There can be many possible solutions for you problem, but I think moving the ajax request block inside reader.onload is better idea since, the variable container will be accessible to child function and it will be called only if vaild file is being uploaded.

$('.image_file').change(function() {    var input = $(this);    var files = this.files;    var total = files.length;    var url = input.attr('data-url');      for (var i = 0; i < total; i++) {      var formData = new FormData();      var file = files[i];      formData.append('image_file', file);      var reader = new FileReader();      reader.onload = function(e) {        var container = $('.photos .photo:not(.active):first');        if (container.length) {          var ajaxFunction = function() {            var myXhr = $.ajaxSettings.xhr();              var progressElem = this.find('progress');              if (myXhr.upload) {              myXhr.upload.addEventListener('progress', function(e) {                if (e.lengthComputable) {                  progressElem.attr({                    value: e.loaded,                    max: e.total                  });                }              }, false);            }              return myXhr;          };          container.css('background-image', 'url(' + e.target.result + ')').addClass('active uploading');          $.ajax({            type: 'post',            url: url,            data: formData,            cache: false,            processData: false,            contentType: false,            xhr: ajaxFunction.bind(container),            success: function(result) {              if (result.status == true) {                $('.success-message').show();              } else {                alert('There was an error uploading your file.');              }            }          });        }      };        reader.readAsDataURL(file);    }  });
.photo {    display: none;    height: 200px;    width: 200px;    float: left;  }  .active {    display: block;  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <input type="file" class="image_file" multiple data-url="http://httpbin.org/post">  <div class="photos">    <div class="photo">      <progress></progress>    </div>    <div class="photo">      <progress></progress>    </div>  </div>

Updated: used bind() function to pass current value of the variable container to the ajaxFunction()

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment