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
- Selecting multiple files at a time
- Selecting from different folders
- Apply validation on files extension, file size and number of uploaded files
- Listing the selected files in thumbnails preview
- Filtering the selected files by Allowing Remove file option
- 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.
<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.
<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>
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.
document.addEventListener("DOMContentLoaded", init, false);
function init() {
document.querySelector('#files').addEventListener('change', handleFileSelect, false);
}
Then, the event handler function:
function handleFileSelect(e) {
if (!e.target.files) return;
var files = e.target.files;
for (var i = 0, f; f = files[i]; i++)
{
var fileReader = new FileReader();
fileReader.onload = (function (readerEvt) {
return function (e) {
ApplyFileValidationRules(readerEvt)
RenderThumbnail(e, readerEvt);
FillAttachmentArray(e, readerEvt)
};
})(f);
fileReader.readAsDataURL(f);
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
}
Thumbnail Preview of Images
After user's selection, we calls fileReader.readAsDataURL()
on the file, and render a thumbnail by setting the 'src
' attribute to a data URL.
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
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:
jQuery(function ($) {
$('div').on('click', '.img-wrap .close', function () {
var id = $(this).closest('.img-wrap').find('img').data('id');
var elementPos = AttachmentArray.map(function (x) { return x.FileName; }).indexOf(id);
if (elementPos !== -1) {
AttachmentArray.splice(elementPos, 1);
}
$(this).parent().find('img').not().remove();
$(this).parent().find('div').not().remove();
$(this).parent().parent().find('div').not().remove();
var lis = document.querySelectorAll('#imgList li');
for (var i = 0; li = lis[i]; i++) {
if (li.innerHTML == "") {
li.parentNode.removeChild(li);
}
}
});
}
)
Validations
I have applied three validation rules on the uploaded files as below:
1. File Types
The allowed file types are: (jpg/png/gif):
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:
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.
function CheckFilesCount(AttachmentArray) {
var len = 0;
for (var i = 0; i < AttachmentArray.length; i++) {
if (AttachmentArray[i] !== undefined) {
len++;
}
}
if (len > 9) {
return false;
}
else
{
return true;
}
}
Then, showing the error message according to validation rules:
function ApplyFileValidationRules(readerEvt)
{
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;
}
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;
}
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:"
if (window.File && window.FileReader && window.FileList && window.Blob)
{
}
else
{
alert('The File APIs are not fully supported in this browser.');
}
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