Monday, November 5, 2018

Ajax File Upload with Dynamic Progress Bar

I tried to make an ajax file upload which will handle multiple file uploads with dynamic progress bars just like uploadify.js. Lets see what I achieved so far and how we can handle ajax file uploads.

Ajax File Upload with Dynamic Progress Bar
Ok, Files we are going to need are index.php which will contain the form code, style.css will contain the styles of page, javascript.js will be holding the code to handle ajax file upload and process-upload.php will process the upload on server side.

index.php

<!DOCTYPE html>
<html>
    <head>
        <title>Ajax File Upload with Dynamic Progress Bar  Demo</title>
        <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
        <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
        <script type="text/javascript" src="js/javascript.js"></script>
        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
        <link rel="stylesheet" href="css/style.css" />
    </head>
    <body>
        <div class="main-container">
            <div class="section">
                <form id="ajax-upload-form" enctype="multipart/form-data">
                    <div class="row">
                        <div class="col-10">
                            <input type="file" class="file-input" name="ajax_file" multiple="multiple"/>
                        </div>
                        <div class="col-2 text-right">
                            <button type="submit" class="btn btn-blue"><i class="fa fa-upload"></i> Upload</button>
                        </div>
                    </div>
                </form>
                <div class="progress-container"></div>
            </div>
        </div>
    </body>
</html>

javascript.js

$(document).ready(function () {
    $("#ajax-upload-form").submit(function (e) {
        e.preventDefault();// Prevent form from being submitted.

        var fd = new FormData();// Create new form data object to push files into
        var files = $(".file-input")[0].files;// Getting all files from input field

        //Loop through all files and append progress bar for each file
        for (var i = 0; i < files.length; i++) {
            fd.append("ajax_file", files[i]);
            var regEx = /^(image|video|audio)\//;

            //Creating the progress bar element
            var bar = '<div class="progress" id="' + i + '">' +
                    '<span class="abort" id="abort-' + i + '">&times;</span>' +
                    '<div class="progress-title" id="progress-title-' + i + '"></div>' +
                    '<div class="progress-bar" id="progress-bar-' + i + '"></div>' +
                    '</div>';
            $(".progress-container").append(bar);// Append progress bar to container
            //If file is not image,audio or upload through add error class to progress otherwise process upload
            if (files[i].type.match(regEx)) {
                //Function to upload single file
                uploadFile(fd, i, files[i]);
            } else {
                $("#progress-bar-" + i).closest(".progress")
                        .addClass("progress-error");
                $("#progress-title-" + i).text("Invalid file format");
            }
            // If all files have been uploaded rest the form
            if (i === (files.length - 1))
                this.reset();
        }
    });
    // Remove progress bar with error class
    $(document).on("click", ".progress-error > .abort", function () {
        $(this).closest(".progress").fadeOut(3000, function () {
            $(this).remove();
        });
    });

});

function uploadFile(fd, i, file) {
    var ajax = $.ajax({
        url: 'process-upload.php',// Server side script to process uploads
        type: 'POST',
        data: fd,
        processData: false,//Bypass jquery's form data processing
        contentType: false,//Bypass jquery's content type to handle file upload
        xhr: function () {
            var xhr = $.ajaxSettings.xhr();
            if (xhr.upload) {
                //Listen to upload progress and update progress bar
                xhr.upload.addEventListener("progress", function (progress) {
                    var total = Math.round((progress.loaded / progress.total) * 100);
                    $("#progress-bar-" + i).css({"width": total + "%"});
                    $("#progress-title-" + i).text(file.name + ' - ' + total + "%");
                }, false);
                //Code to be executed if upload is aborted
                xhr.addEventListener("abort", function () {
                    $("#progress-bar-" + i).closest(".progress").fadeOut(3000, function () {
                        $(this).remove();
                    });
                }, false);
                //Update progress and remove it after upload has finished
                xhr.addEventListener("loadend", function () {
                    $("#progress-bar-" + i).closest(".progress").fadeOut(3000, function () {
                        $(this).remove();
                    });
                }, false);

                //Show an error on progress if an error has occured during upload
                xhr.addEventListener("error", function () {
                    $("#progress-bar-" + i).closest(".progress")
                            .addClass("progress-error").find("status-count").text("Error");
                }, false);
                //Show timeout error on progress bar if upload request has timedout
                xhr.addEventListener("timeout", function (progress) {
                    $("#progress-bar-" + i).closest(".progress")
                            .addClass("progress-timedout").find("status-count").text("Timed Out");
                }, false);
            }
            return xhr;
        }
    });
    // Bind abort to current ajax request.
    $(document).on("click", ".progress > #abort-" + i, function () {
        ajax.abort();
    });
}

style.css

*{
    box-sizing: border-box;
}
html,body{
    margin: 0px;
    padding: 0px;
}
body{
    background: #f0f0f0;
    font: normal normal 14px Open Sans,Verdana, Arial;
}
.main-container{
    max-width: 1024px;
    margin: 0px auto;
}
.section {
    padding: 15px;
}
.text-right{
    text-align: right;
}
.row:before,
.row:after{
    content: ' ';
    display: table;
}
.row:after{
    clear:both;
}
.row{
    margin-left: -10px;
    margin-right: -10px;
    margin-bottom: 10px;
}
[class*="col-"] {
    float: left;
    padding: 0px 10px;
}
.col-2{
    width: 16.66%;
}
.col-10{
    width: 83.33%;
}
.btn{
    display: inline-block;
    padding: 5px 10px;
    cursor: pointer;
}
.btn-blue{
    background: #3c8dbc;
    border: 1px solid #2b7cab;
    color: #fff;
}
.progress{
    background: #fff;
    border: 1px solid  #00a65a;
    margin-bottom: 10px;
    padding: 5px;
}
.progress > .abort{
    float: right;
    font-size: 20px;
    cursor: pointer;
    line-height: 18px;
    opacity: 0.5;
}
.progress > .abort:hover{
    opacity: 1;
}
.progress-error{
    border: 1px solid #e65442;
    color: #e65442;
}
.progress > .progress-bar{
    width: 0%;
    transition: width ease .1s;
    background-color: #00a65a;
    max-width: 100%;
    height: 5px;
    margin-top: 5px;
}
.progress-error > .progress-bar{
    background-color: #e65442;
}

process-upload.php

<?php
// Code to process uploads
if($_FILES && !$_FILES['ajax_file']['error']){
    $allowed_types = ["audio","image","video"];
    $type = substr($_FILES['ajax_file']['type'],0,5);
    //Check if uploaded file is image,audio or video then save file
    if(in_array($type,$allowed_types)){
        move_uploaded_file($_FILES['ajax_file']['tmp_name'],"uploaded-files/".$_FILES['ajax_file']['name']);
    }
}
?>