0

I have an Html input file that lets upload images and is sand with ajax to asp.net MVC controller, everything is working fine.

But I'm having trouble with updating a simple progress bar.

This is the simple Html,

<input type="file" multiple="multiple" id="file_upload" />
<br />
<input type="button" id="upload" name="upload" value="Upload" class="btn btn-primary btn-block" />
<br />
<div class="outter">
    <div class="inner" id="progress"></div>
</div>
<br />

In my javascript, I am splitting the request to upload 10 images at a time, here is how it looks,

<script>
var counter = 0;

$("#upload").click(function () {
    var fileUpload = $("#file_upload").get(0);
    var files = fileUpload.files;
    var devide = devider(files.length);

    for (var x = 0; x < devide; x++) {
        var fileData = new FormData();
        for (var i = 0; i < files.length / devide; i++) {
            if (counter < files.length) {
                fileData.append(files[counter].name, files[counter]);
                counter++;                  
            }
        }

        $.ajax({
            type: "POST",
            url: "/Upload/GetFiles",
            data: fileData,
            cache: false,
            contentType: false,
            processData: false,
            async: false,
            success: function (result) {
                console.log(result);
            },
            error: function (xhr, status, error) { console.log('Error:' + error); },
            timeout: 0
        });
    }
});

function devider(number) {
    var count = 0;
    for (var i = 0; i < number; i+=10) {
        count++;
    }
    return count;
}
</script>

I just need to update the width of the <div id="progress"> each time a file is uploaded which would probably mean in the second for loop. but I can't since it is in a loop it is not updating the HTML?

p.s. I am aware of setInterval but I don't know how to implement it in this situation!?

Tom O.
  • 5,730
  • 2
  • 21
  • 35
scg
  • 21
  • 9
  • `async: false,` is why it doesn't appear to be updating the html. It likely is updating the html, but because you're locking up the browser with a synchronous ajax request, it doesn't get to render the change until all the ajax is complete. – Kevin B Apr 19 '18 at 20:19
  • @KevinB if `asynk: ture` i would get an error since it is making the second post request before the first returns success which results in a 500 internal server error. – scg Apr 22 '18 at 14:22
  • redo the logic such that that isn’t a problem. a progress bar can’t work with synchronous code. – Kevin B Apr 22 '18 at 15:51
  • @KevinB even if `async: true,` it won't update the HTML until the loop is finished :( I mentioned below I think a for loop is blocking the page while it is in process. – scg Apr 22 '18 at 16:48
  • yeah, the for loop has to go too. – Kevin B Apr 22 '18 at 18:21

2 Answers2

1

Probably the answer to this question is, That you can't update any HTML within a JavaScript for loop!

I don't know why it is not covered more, but I tried everything that's possible I tried in Chrome and in Fiddle, the HTML on the page will not get updated until the loop is finished executing it doesn't matter if you try from the loop itself, or you call an outside function, again the HTML will not redraw until the loop is finished executed.

Then I found this post which explains it a little

Here is a quote,

JavaScript execution and page rendering are done in the same execution thread, which means that while your code is executing the browser will not be redrawing the page. (Though even if it was redrawing the page with each iteration of the for loop it would all be so fast that you wouldn't really have time to see the individual numbers.)

So I had to drop the for loop in order to update the HTML, this is how my JavaScript looks now.

<script>

    var counter = 0;
    var index = 0;
    var fileUpload;
    var files;
    var devide;
    var add_width;
    var update_width = 0;
    var fileData;
    var interval;

    document.getElementById("file_upload").onchange = function () {
        fileUpload = $("#file_upload").get(0);
        files = fileUpload.files;
        devide = devider(files.length);
        add_width = 100 / devide;
        $("#progress").css({ "width": "0%", "max-width": "100%" });
    }

    $("#upload").click(function () {
        if (fileUpload) {
            $("#outter").css({ "display": "inline-block" });
            prepareUpload();
        }        
    })

    function prepareUpload() {

        fileData = new FormData();
        for (var i = 0; i < 10; i++) {
            if (counter < files.length) {
                fileData.append(files[counter].name, files[counter]);
                counter++;
            }      
        }
        update_width += add_width;
        document.getElementById("progress").style.width = update_width + "%";
        document.getElementById("text").innerHTML = Math.round(update_width) + " % Completed";
        interval = setInterval(upload, 90);  
    }

    function upload() {
        if (index < devide) {
            $.ajax({
                type: "POST",
                url: "/Upload/GetFiles",
                data: fileData,
                cache: false,
                contentType: false,
                processData: false,
                async: false,
                complete: function () {
                },
                success: function (result) {
                    if (result < files.length) {
                        prepareUpload();
                    } else {
                        clearInterval(interval);
                    }
                },
                error: function (xhr, status, error) { console.log('Error:' + error); },
                timeout: 0
            });
        } else {
            clearInterval(interval);
        }
        index++;       
    }

    function devider(number) {
        var count = 0;
        for (var i = 0; i < number; i += 10) {
            count++;
        }
        return count;
    }

</script>
scg
  • 21
  • 9
0

I would use an already nicely made progressbar. But here is an answer to your question

Edit: Added a JSFiddle for you, with a dummy progress bar: https://jsfiddle.net/strauman/wy7w015e/

You say you want the progress bar to update after a file upload has succeeded. So let's make a function prepare_progressbar to ready the number of steps and a function update_progressbar for updating it.

First we make variables to contain values we'd need in both our funcitons:

var width_per_file;
var number_of_files_completed;
var number_of_files_to_upload=0;

Now we "prepare" it by checking how many files are getting uploaded, and initialising the variables.

function prepare_progressbar(number_of_files){
  var max_progressbar_width=$(window).width();
  // Reset the completed-counter
  number_of_files_completed=0;
  // How much should it increase per file completed?
  width_per_file=$(window).width()/number_of_files;
  // Set the global number of files
  number_of_files_to_upload=number_of_files;
  // Make progress bar "hidden"
  $("#progress").css({"max-width": "0px"});
}

Now, we want to make a function that updates the progress bar. Every time the ajax completes a request we want it to call this function:

function update_progressbar(){
  number_of_files_completed+=1;
  var new_progressbar_width=number_of_files_completed*width_per_file;
  $("#progress").css({"width":new_progressbar_width+"px", "max-width":new_progressbar_width+"px"});
}

Now the only thing remaining is to insert these functions in the proper places in your code. It all comes together like so:

var width_per_file;
var number_of_files_completed;
var number_of_files_to_upload=0;

function prepare_progressbar(number_of_files){
  var max_progressbar_width=$(window).width();
  // Reset the completed-counter
  number_of_files_completed=0;
  // How much should it increase per file completed?
  width_per_file=$(window).width()/number_of_files;
  // Set the global number of files
  number_of_files_to_upload=number_of_files;
  // Make progress bar "hidden"
  $("#progress").css({"max-width": "0px"});
}

function update_progressbar(){
  number_of_files_completed+=1;
  var new_progressbar_width=number_of_files_completed*width_per_file;
  $("#progress").css({"width":new_progressbar_width+"px", "max-width":new_progressbar_width+"px"});
}

var counter = 0;

$("#upload").click(function () {
    var fileUpload = $("#file_upload").get(0);
    var files = fileUpload.files;
    var devide = devider(files.length);
    prepare_progressbar(devide);
    for (var x = 0; x < devide; x++) {
        var fileData = new FormData();
        for (var i = 0; i < files.length / devide; i++) {
            if (counter < files.length) {
                fileData.append(files[counter].name, files[counter]);
                counter++;
            }
        }

        $.ajax({
            type: "POST",
            url: "/Upload/GetFiles",
            data: fileData,
            cache: false,
            contentType: false,
            processData: false,
            async: false,
            // On complete, it will call our update_progressbar
            complete: update_progressbar,
            success: function (result) {
                console.log(result);
            },
            error: function (xhr, status, error) { console.log('Error:' + error); },
            timeout: 0
        });
    }
});

function devider(number) {
    var count = 0;
    for (var i = 0; i < number; i+=10) {
        count++;
    }
    return count;
}
  • Thank you! i'm trying your code now. But i don't understand why when you call this function `prepare_progressbar(devide);` you pass in devide? this isn't the amount of file, why not files.length!? – scg Apr 19 '18 at 20:05
  • Sorry my bad now get it. It's only updating each chunk not each file. – scg Apr 19 '18 at 20:23
  • @scg Don't forget to upvote and/or accept answer if it's satisfactory! – Andreas Storvik Strauman Apr 19 '18 at 20:28
  • since you call the update function from the ajax call which runs only the same amount as the devide variable it makes more sense the first version. It was my mistake. Unless you run the update function from the second loop... – scg Apr 19 '18 at 20:29
  • i still can't get to work :( when i do `console.log(number_of_files_completed)` it shows it is geting updated but on screen it is not. Still working on it... – scg Apr 19 '18 at 20:33
  • Make sure you're actually adding styles to your progressbar, so it's not just a blank `div` spanning across the page. – Andreas Storvik Strauman Apr 19 '18 at 20:34
  • `.inner { background-color: darkcyan; height: 13px; }` this is my scc. Do i miss somthing? – scg Apr 19 '18 at 20:38
  • man I'm struggling ;( not even the `$("#progress").css({ "width": "0px","max-width": "0px" });` is getting excuted!? in console everything is working but in the html nothing is??? only at the end the div is flikering!? what am i doing wrong? – scg Apr 19 '18 at 21:24
  • I made a [fiddle](https://jsfiddle.net/strauman/wy7w015e/) for you: https://jsfiddle.net/strauman/wy7w015e/ – Andreas Storvik Strauman Apr 20 '18 at 18:21
  • thanks man nice fiddle! but you didn't use a loop u used a `setTimeout`. I have tried using `setTimeout` but it is not executing all the code. If i use `setInterval` the code gets executed fine but it is making a lot more requests to the server than necessary as it is nicely explained [here](https://stackoverflow.com/a/731625/9329277) – scg Apr 22 '18 at 14:38
  • It would be exactly the same result if I used a loop, but I would ask it to sleep, because the file upload would go so fast. To use `setInterval`, you'd need a whole other approach. – Andreas Storvik Strauman Apr 22 '18 at 15:30
  • I think it won't work the same, anything that it's in a for loop won't get updated in the actual HTML, I think the for loop blocks the page until the loop is finished. If possible show me a fiddle that is updating HTML within a for loop!? – scg Apr 22 '18 at 16:35