Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Lightweight Multiple File Upload with Thumbnail Preview using HTML5 and Client Side Scripts

4.93/5 (16 votes)
11 Oct 2017CPOL3 min read 65.1K   4.8K  
This article describes how to add a fully client side Multiple File Upload functionality to your page. Include select multiple files at a time, Select from different folders, Apply validation, show files in thumbnails and remove files from the list.

Image 1

Introduction

Uploading files as attachments is a common scenario and an essential part of many web applications.
I was in a situation where I needed a multiple file upload functionality on an HTML page that needed to be submitted one time to the server.

The beauty of this solution that it works completely from the client side. You can select multiple files at a time, select from different folders, apply validation, view the selected files in thumbnails preview and allow you to remove files from list. Therefore, it is very lightweight, because all of the overhead will remain on the client side.

Solution Summary

  1. Selecting multiple files at a time
  2. Selecting from different folders
  3. Apply validation on files extension, file size and number of uploaded files
  4. Listing the selected files in thumbnails preview
  5. Filtering the selected files by Allowing Remove file option
  6. Save the selected and filtered list of files in JavaScript array to be submitted to server

Background

You need to have a basic understanding of HTML5, CSS and client side script (JavaScript and JQuery).

Using the Code

Technical Introduction

HTML5 provides a standard way to deal with local files via via File API. File API can be used to create thumbnail preview of images, also you can use client-side logic to validate the uploaded files type, size and files count.

Selecting Files

I have used the standard HTML5 control for multiple file upload. This is the most straightforward way to upload a files element.

HTML
<input id="files" multiple="multiple" name="files[]" type="file" />    

To give the file upload control a modern look, I add a stylesheet for the file upload parent span.

HTML
<span class="btn btn-success fileinput-button">
    <span>Select Attachment</span>
        <input accept="image/jpeg, image/png, image/gif," 
         id="files" multiple="multiple" name="files[]" type="file" />
</span>

Image 2

After user selection; JavaScript returns the list of selected File objects as a FileList. I added an event handler for the file upload control to access the FileList properties.

JavaScript
document.addEventListener("DOMContentLoaded", init, false);
    
function init() {
    document.querySelector('#files').addEventListener('change', handleFileSelect, false);
}

Then, the event handler function:

JavaScript
function handleFileSelect(e) {
    //to make sure the user select file/files
    if (!e.target.files) return;

    //To obtain a File reference
    var files = e.target.files;

    // Loop through the FileList and then to render image files as thumbnails.
    for (var i = 0, f; f = files[i]; i++) 
    {
        //instantiate a FileReader object to read its contents into memory
        var fileReader = new FileReader();

        // Closure to capture the file information and apply validation.
        fileReader.onload = (function (readerEvt) {
            return function (e) {
                
                //Apply the validation rules for attachments upload
                ApplyFileValidationRules(readerEvt)

                //Render attachments thumbnails.
                RenderThumbnail(e, readerEvt);

                //Fill the array of attachment
                FillAttachmentArray(e, readerEvt)
            };
        })(f);

        // Read in the image file as a data URL.
        // readAsDataURL: The result property will contain the file/blob's data encoded as a data URL.
        // More info about Data URI scheme https://en.wikipedia.org/wiki/Data_URI_scheme
        fileReader.readAsDataURL(f);
    }
    document.getElementById('files').addEventListener('change', handleFileSelect, false);
}

Thumbnail Preview of Images

Image 3

After user's selection, we calls fileReader.readAsDataURL() on the file, and render a thumbnail by setting the 'src' attribute to a data URL.

JavaScript
//Render attachments thumbnails.
function RenderThumbnail(e, readerEvt)
{
    var li = document.createElement('li');
    ul.appendChild(li);
    li.innerHTML = ['<div class="img-wrap"> <span class="close">×</span>
                   <img class="thumb" src="', e.target.result, '" title="', escape(readerEvt.name), 
                    '" data-id="',readerEvt.name, '"/></div>'].join('');
 
    var div = document.createElement('div');
    div.className = "FileNameCaptionStyle";
    li.appendChild(div);
    div.innerHTML = [readerEvt.name].join('');
    document.getElementById('Filelist').insertBefore(ul, null);
}

Remove Files

Image 4

I have added the ability to remove files by adding <span class="close">×</span> (&-times; means x in HTML code) to each image preview. So when a user clicks on the red (x), it will be removed by calling the below Jquery function:

JavaScript
//To remove attachment once user click on x button
jQuery(function ($) {
    $('div').on('click', '.img-wrap .close', function () {
        var id = $(this).closest('.img-wrap').find('img').data('id');
 
        //to remove the deleted item from array
        var elementPos = AttachmentArray.map(function (x) { return x.FileName; }).indexOf(id);
        if (elementPos !== -1) {
            AttachmentArray.splice(elementPos, 1);
        }
 
        //to remove image tag
        $(this).parent().find('img').not().remove();
 
        //to remove div tag
        $(this).parent().find('div').not().remove();
 
        //to remove div tag
        $(this).parent().parent().find('div').not().remove();
 
        //to remove li tag
        var lis = document.querySelectorAll('#imgList li');
        for (var i = 0; li = lis[i]; i++) {
            if (li.innerHTML == "") {
                li.parentNode.removeChild(li);
            }
        }
 
    });
}
)

Validations

Image 5

I have applied three validation rules on the uploaded files as below:

1. File Types

The allowed file types are: (jpg/png/gif):

JavaScript
//To check file type according to upload conditions
function CheckFileType(fileType) {
    if (fileType == "image/jpeg") {
        return true;
    }
    else if (fileType == "image/png") {
        return true;
    }
    else if (fileType == "image/gif") {
        return true;
    }
    else {
        return false;
    }
    return true;
}

2. File Size

Uploaded file size should not exceed 300 KB for each:

JavaScript
//To check file Size according to upload conditions
function CheckFileSize(fileSize) {
    if (fileSize < 300000) {
        return true;
    }
    else {
        return false;
    }
    return true;
}

3. Files Count

The number of uploaded files count should not exceed 10.

JavaScript
//To check files count according to upload conditions
function CheckFilesCount(AttachmentArray) {
    //Since AttachmentArray.length return the next available index in the array,
    //I have used the loop to get the real length
    var len = 0;
    for (var i = 0; i < AttachmentArray.length; i++) {
        if (AttachmentArray[i] !== undefined) {
            len++;
        }
    }
    //To check the length does not exceed 10 files maximum
    if (len > 9) {
        return false;
    }
    else
    {
        return true;
    }
}

Then, showing the error message according to validation rules:

JavaScript
//Apply the validation rules for attachments upload
function ApplyFileValidationRules(readerEvt)
{
    //To check file type according to upload conditions
    if (CheckFileType(readerEvt.type) == false) 
    {
        alert("The file (" + readerEvt.name + ") does not match the upload conditions, 
                             You can only upload jpg/png/gif files");
            e.preventDefault();
            return;
    }

    //To check file Size according to upload conditions
    if (CheckFileSize(readerEvt.size) == false) 
    {
        alert("The file (" + readerEvt.name + ") does not match the upload conditions, 
               The maximum file size for uploads should not exceed 300 KB");
        e.preventDefault();
        return;
    }

    //To check files count according to upload conditions
    if (CheckFilesCount(AttachmentArray) == false) 
    {
        if (!filesCounterAlertStatus) 
        {
            filesCounterAlertStatus = true;
            alert("You have added more than 10 files. According to upload conditions, 
                   you can upload 10 files maximum");
        }
        e.preventDefault();
        return;
    }
}

Points of Interest

The final output from the client side will be a JavaScript array. You can send it to server side according to your solution architecture. For example, you can send it with JSON object, or save it in an HTML hidden field and read it from there.

For the upload rules like (number of files, file size and file type), I recommend keeping it in config file and read it from there.

If you are dealing with big size attachment, then you can add a progress bar that is supported by HTML5 also. It allows you to monitor the progress of a file read; useful for large files, catching errors, and figuring out when a read is complete. Read this for more information.

Browser Compatibility

This control has been tested on the latest version of Firefox, Internet Explorer, Chrome and Safari
"For old browsers version, you can check that browser fully supports the File API as below:"

JavaScript
// Check for the various File API support.
if (window.File && window.FileReader && window.FileList && window.Blob) 
{
    // Great success! All the File APIs are supported.
} 
else 
{
  alert('The File APIs are not fully supported in this browser.');
}
//source: https://www.html5rocks.com/en/tutorials/file/dndfiles/

Finally

I tried my best to make the code easy and clear. For further improvement; any comments, ideas, and suggestions are most welcome. Please provide a "Vote" if this would be helpful.

References

History

  • Version 1.0: 12/10/2017

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)