0

How do you update multiple progress bars when posting a form via ajax? Here is the code I have but I can't figure it out:

Form code:

<form id="upload-form" class="no-padding" method="post" enctype="multipart/form-data">
    <p><label for="folder">Create folder:</label><input type="text" name="folder" placeholder="Enter a folder name"></p>
    <p><label for="file">Create file:</label><input type="text" name="file" placeholder="Enter a file name with extension (e.g. home.php)"></p>
    <p class="no-margin">Upload file(s):</p>
        <div class="custom-upload"> 
            <input id="upload" type="file" name="upload[]" multiple>
            <div class="fake-file">
                <a class="browse text-center"><i class="fa fa-list"></i> Browse</a><input placeholder="No file(s) selected..." disabled="disabled" >
            </div>
        </div>
    <div id="selectedFiles" class='selectedFiles'></div>
    <?php echo "<input name='url' hidden value='" . $_SERVER['REQUEST_URI'] ."'>";?>
    <button id="submit" name="submit"><i class="fa fa-upload"></i> Upload</button>
    <p id="uploading" class='success text-right' hidden>Please be patient while your files are uploading.</p>
</form>

Javascript code:

var selDiv = "";
document.addEventListener("DOMContentLoaded", init, false);
function init() {
    document.querySelector('#upload').addEventListener('change', handleFileSelect, false);
    selDiv = document.querySelector("#selectedFiles");
}

var files, filesToUpload;

// populates files into array (filesToUpload) and displays selected files to the user
function handleFileSelect(e) {
    if(!e.target.files) return;
    selDiv.innerHTML = "";
    var files = e.target.files;
    filesToUpload = Array.prototype.slice.call(files);
    if (files.length > 0) {
        selDiv.innerHTML += '<p id="file-upload-paragraph" class="no-padding no-margin">Files selected for upload. Click the <b>x</b> to cancel file upload for a specific file:</p>';
    }
    for(var i = 0; i < files.length; i++) {
        var f = files[i];
        selDiv.innerHTML += '<div class="selectedFiles"><a href="#" class="cancel text-center"><i class="fa fa-remove"></i></a><progress id="progress' + i + '" class="text-right" value="0" hidden></progress><span class="file-holder">' + f.name + ' <i class="fa fa-file"></i></span></div>';
    }
}

// removes user selected file before upload
$(document).on('click', '.cancel', function () {
    filesToUpload.splice($(".cancel").index(this), 1);
    $(this).closest('div').remove();
    if (filesToUpload.length == 0) {
        $('#file-upload-paragraph').remove();
        $('.custom-upload input[type=file]').val('');
    }
    $('.custom-upload input[type=file]').next().find('input').val(filesToUpload.length + ' files selected!');
});

// sets progress bar for each loaded file
$('#upload-form').submit(function(e){
    e.preventDefault();
    var url = location.href;
    $("#upload").remove();
    $(".cancel").hide();
    $("progress").show();
    var data = new FormData($('form')[0]);
    if (filesToUpload.length != 0) {
        for (var i = 0, j = filesToUpload.length; i < j; i++) {
            data.append("upload[]", filesToUpload[i]);
        }
    }
    $.ajax({
        url: url,
        type: 'POST',
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        xhr: function(progress) { 
                for (var i = 0, j = filesToUpload.length; i < j; i++) {
                    var progressBar = 'progress' + i;
                    if(document.getElementById(progressBar) == null) {
                        j++;
                        continue;
                    }
                    var xhr = new XMLHttpRequest();
                    (function(progressBar) { 
                        xhr.upload.onprogress = function(e) {
                            $('#' + progressBar).attr({value: e.loaded, max: e.total});
                        };
                    }(progressBar));
                }
                return xhr;
             },
        success: function(res){
            if(!res.error) location.reload(true);
        }
    });
});

PHP code:

// function call
uploadFiles(count($_FILES["upload"]["name"]), $_FILES["upload"]["tmp_name"], $_FILES["upload"]["name"], $path);

// function that uploads selected files
function uploadFiles($total, $tmpFiles, $uploadedFiles, $path) {
    for($i=0; $i < $total; $i++) {
        $tmpFilePath = $tmpFiles[$i];
        if ($tmpFilePath != ""){
            $newFilePath = "$path/" . $uploadedFiles[$i];
            if(file_exists($newFilePath)) {
                unlink($newFilePath);
            }
            move_uploaded_file($tmpFilePath, $newFilePath);
        }
    }
}

Here is a picture of the form, just in case: Form image

Thanks in advance for any help.

Dustin
  • 123
  • 1
  • 10
  • Do you have to support MSIE < 10 / non HTML5 user-agents? – Kevin_Kinsey Jun 27 '16 at 20:06
  • You need to send it as multipart/form-data. – Hoyen Jun 27 '16 at 20:10
  • @Kkinsey: Thank goodness no. – Dustin Jun 27 '16 at 20:10
  • @Hoyen: I have included enctype="multipart/form-data" in the form. What else do I need? – Dustin Jun 27 '16 at 20:14
  • @DustinOrmond You aren't submitting it with the form. You are submitting it using JavaScript/AJAX. This solution might help: http://stackoverflow.com/questions/5392344/sending-multipart-formdata-with-jquery-ajax – Hoyen Jun 27 '16 at 20:15
  • @Hoyen I have updated the code above based on the link you provided and others. However, for some reason I still can't get it to work. The ajax success response updates the text in my browser; however, PHP doesn't recognize it. – Dustin Jun 30 '16 at 15:13
  • @DustinOrmond does your PHP work correctly? I would suggest creating a test page with a form that submits a file without using javascript. Another words point the form's action to the php and have a submit button that will submit the file to it and see if the PHP behaves as expected. – Hoyen Jun 30 '16 at 15:30
  • @Hoyen Awesome! Thank you so much for your help. I have got everything working except my progress bars. Do you know of a way to update multiple progress bars via ajax? I have updated my code to reflect what I am trying to do. If I run the code outside of AJAX it updates them properly, but inside AJAX it only updates the last progress bar. I know it has to do something with the return statement, but I can't figure it out. – Dustin Jun 30 '16 at 19:11
  • @Dusting I''m not aware of a way to detect each file upload individually with just a single AJAX upload request. If each file needs it's own progress bar, you would need to make multiple AJAX requests for each one. – Hoyen Jun 30 '16 at 21:36
  • Okay. Thanks for your help. – Dustin Jun 30 '16 at 22:31
  • @Hoyen FYI, I figured it out in case you are curious. See my answer below. – Dustin Jul 01 '16 at 07:25
  • @Dustin yes, that's what I meant when I said you would need to make multiple AJAX requests to do it. – Hoyen Jul 01 '16 at 13:03
  • @Hoyen Do you know why my code works in Chrome but no other browser? – Dustin Jul 02 '16 at 15:47
  • Figured it out!!! In my PHP script, instead of using `if(isset($_POST['value'])) {...}`, I am now using `if ($_SERVER['REQUEST_METHOD'] == 'POST') {...}` which works across multiple browsers. – Dustin Jul 03 '16 at 04:45

1 Answers1

0

I don't know if anyone will be looking to do the same thing but I found my own answer. Basically, replace my initial code for catching when the form is submitted with the following:

// sets progress bar for each loaded file
$('#upload-form').submit(function(e){
    e.preventDefault(); // removes the default behavior of the form button
    var url = location.href; // returns the current location
    $("#upload").remove(); // removes the file upload box which is replaced with an array in my other code
    $(".cancel").hide(); // hides the cancel buttons
    $("progress").show(); // shows the hidden progress bars

    // checks to see if files were selected for being uploaded
    if (filesToUpload.length != 0) {
        var progressBars = [];

        // this loop accounts for files that were deleted after selection
        for (var i = 0, j = filesToUpload.length; i < j; i++) {
            if(document.getElementById('progress' + i) == null) {
                j++;
                continue;
            }
            progressBars.push(i);
        }

        // call to postNext function
        postNext(0, progressBars, url);

    // executes when only the other form elements are submitted with no file upload
    } else {
        var data = new FormData($('form')[0]);
        $.ajax({
            url: url,
            type: 'POST',
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            success: function(res){
                if(!res.error) location.reload(true);
            }       
        });
    }
});

// posts each file separately
function postNext(i, progressBars, url) {

    // continues as long as there are more files to display progress bars
    if (i < progressBars.length) {
        var index = progressBars[i];
        var data = new FormData($('form')[0]);

        // after first ajax send, resets form so only the remaining file uploads are resubmitted
        if (i == 0) {
            $("#upload-form")[0].reset();
        }
        data.append("upload[]", filesToUpload[i]); //append the next file
        $.ajax({
            url: url, // url for post
            type: 'POST',
            data: data, 
            cache: false,
            contentType: false,
            processData: false,
            xhr: function(progress) { 
                    // set the progress for a given progress bar
                    var xhr = new XMLHttpRequest();
                    var progressBar = 'progress' + index;
                    (function(progressBar) { 
                        xhr.upload.onprogress = function(e) {
                            $('#' + progressBar).attr({value: e.loaded, max: e.total});
                        };
                    }(progressBar));
                return xhr;
            },
            beforeSend: postNext(i + 1, progressBars, url) // begins next progress bar
        });
    }
}

// refreshes the page only after all ajax requests are completed
$(document).bind("ajaxSend", function () {
    console.log("waiting for all requests to complete...");
}).bind("ajaxStop", function () {
    location.reload(true);
});
Dustin
  • 123
  • 1
  • 10
  • I saw your `Form image`. Can I get the full code, please? I want to implement something like that. A user can select multiple images and he can remove the images if they don't want. Only selected files will be uploaded to the server with the progress bar. – acmsohail Apr 18 '18 at 13:13
  • What you see is the full code. You just style it with CSS – Dustin Apr 20 '18 at 17:18
  • Sorry for the delay. Life has been really crazy that I had to disregard this. Anyway, I think your error is in the PHP code. You need to specify the path where you want the files to be uploaded to prior to uploading the file: `$path = "[PUT PATH HERE]";` – Dustin May 20 '18 at 00:14