Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: tracking status of each file being uploaded simultaneously

Upon pressing a submit button, an array of files ($scope.files, can be as little as one file, as many as the user wants) gets submitted via FormData and XMLHttpRequest in my angular function $scope.uploadFiles:

$scope.uploadFiles = function() {     for (var i in $scope.files) {         var form = new FormData();         var xhr = new XMLHttpRequest;              // Additional POST variables required by the API script         form.append('destination', 'workspace://SpacesStore/' + $scope.currentFolderUUID);         form.append('contenttype', 'idocs:document');         form.append('filename', $scope.files[i].name);         form.append('filedata', $scope.files[i]);         form.append('overwrite', false);          xhr.upload.onprogress = function(e) {             // Event listener for when the file is uploading             $scope.$apply(function() {                 var percentCompleted;                 if (e.lengthComputable) {                     percentCompleted = Math.round(e.loaded / e.total * 100);                     if (percentCompleted < 1) {                                             // .uploadStatus will get rendered for the user via the template                         $scope.files[i].uploadStatus = 'Uploading...';                     } else if (percentCompleted == 100) {                         $scope.files[i].uploadStatus = 'Saving...';                     } else {                         $scope.files[i].uploadStatus = percentCompleted + '%';                     }                 }             });         };          xhr.upload.onload = function(e) {             // Event listener for when the file completed uploading             $scope.$apply(function() {                 $scope.files[i].uploadStatus = 'Uploaded!'                 setTimeout(function() {                     $scope.$apply(function() {                         $scope.files[i].uploadStatus = '';                     });                 }, 4000);             });         };          xhr.open('POST', '/path/to/upload/script');         xhr.send(form);     }  } 

The problem is that var i increments in the initial for loop for each file, and by the time the event listeners fire, i has already incremented past the intended files[i] value needed, only effecting the last in the array. I use .uploadStatus as a means to interactively show the progress of each individual file to the user, therefor I would need to have separate event listeners for each array element in $scope.files. How would I assign and track events for individual array elements in Angular?

UPDATE


I reworked the two event listeners, with a little success, but I'm still experiencing odd behavior:

xhr.upload.onprogress = (function(file) {     // Event listener for while the file is uploading     return function(e) {         $scope.$apply(function() {             var percentCompleted = Math.round(e.loaded / e.total * 100);             if (percentCompleted < 1) {                 file.uploadStatus = 'Uploading...';             } else if (percentCompleted == 100) {                 file.uploadStatus = 'Saving...';             } else {                 file.uploadStatus = percentCompleted + '%';             }         });     } })($scope.files[i]);  xhr.upload.onload = (function(file, index) {     // Event listener for when the file completed uploading     return function(e) {         $scope.$apply(function() {             file.uploadStatus = 'Uploaded!'             setTimeout(function() {                 $scope.$apply(function() {                     $scope.files.splice(index,1);                 });             }, 2000);         });     } })($scope.files[i], i); 

.onprogress seems to go off without a hitch, but with some small changes made to .onload, I am now seeing a lot of weird behavior with AngularJS's two-way binding for its templates. for each elemnt in array $scope.files a status is given, using the the aforementioned .uploadStatus property. Now I am having the setTimeout splice the elements from the array, via the i variable that's getting passed in to the self-executing function. Oddly enough the uploads are capping at around 6 simultaneous uploads at a time now, that must be a server side issue, but I'm noticing that as array elements are getting spliced, the ng-repeat in the template is acting bizarrely where it will splice an element, not necessarily the ones it should. I've also noticed that there are often entries that do not get spliced after the 2000 millisecond threshold is hit.

This is reminiscent of the original problem where the variable i is unreliable when referenced throughout the triggering of the event listeners? Now I am passing it in to the anonymous self-executing .onload function, and the splice is using it to determine what array element it should remove, but it isn't necessarily removing the correct one, and often leaving other elements in the array when it should be removing them.

like image 636
Scott Avatar asked Nov 27 '12 19:11

Scott


2 Answers

The index you pass to the event handlers refer to the indices in the original array. After every successful call to splice, the array changes and indices are no longer pointing to the same thing.

$scope.files = ['a.jpg', 'b.jpg', 'c.jpg']; // $scope.files[1] = 'b.jpg'  $scope.files.splice(1,1); // $scope.files = ['a.jpg', 'c.jpg'] // $scope.files[1] = 'c.jpg' 
like image 57
abhaga Avatar answered Sep 28 '22 16:09

abhaga


AngularJs 1.5.5 changes the approach by providing callback eventhandlers for upload progress.

Reference - How to track progress using eventHandlers and uploadEventHandlers in angularjs 1.5.5 with $http or $resource?

var uploadData = new FormData(); uploadData.append('file', obj.lfFile); var fileData = angular.toJson({     'FileName': obj.lfFile.name,     'FileSize': obj.lfFile.size }); uploadData.append('fileData', fileData)  $http({     method: 'POST',     url: vm.uploadPath,     headers: {         'Content-Type': undefined,         'UserID': vm.folder.UserID,         'ComputerID': vm.folder.ComputerID,         'KeepCopyInCloud': vm.keepCopyInCloud,         'OverWriteExistingFile': vm.overwriteExistingFile,         'RootFileID': vm.folder.RootFileID,         'FileName': obj.lfFile.name,         'FileSize': obj.lfFile.size     },     eventHandlers: {         progress: function(c) {             console.log('Progress -> ' + c);             console.log(c);         }     },     uploadEventHandlers: {         progress: function(e) {             console.log('UploadProgress -> ' + e);             console.log(e);         }     },     data: uploadData,     transformRequest: angular.identity }).success(function(data) {     console.log(data); }).error(function(data, status) {     console.log(data);     console.log(status); }); 
like image 29
appbootup Avatar answered Sep 28 '22 18:09

appbootup