Learn how to create custom fileuploader with Jquery, ES6 and Bootstrap4.

We will create a file uploader with custom design and an option to preview selected files and remove them.

Demo

Check out the live demo here.

Implementation

We will use html5 fileuploader to upload the files.

Then with the help of Bootstrap popover, we will preview the selected files.

While previewing the file we will provide an option to remove the selected file.

As Jquery is one of the dependencies for Bootstrap popover, we will use to ease our work.

Dependencies

<!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"> <!-- Font Awesome --> <script defer src="https://use.fontawesome.com/releases/v5.7.2/js/all.js" integrity="sha384-0pzryjIRos8mFBWMzSSZApWtPl/5++eIfzYmTgBBmXYdhvxPc+XcFEk+zJwDgWbP" crossorigin="anonymous"></script> <!-- jQuery library --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <!-- Popper JS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script> <!-- Latest compiled JavaScript --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>

HTML layout for fileuploader

<div class="custom-file-picker"> <div class="picture-container form-group"> <h4 class="info_text">Upload Proof of Physical Address</h4> <div class="picture"> <span class="icon"><i class="fas fa-file-upload"></i></span> <input type="file" class="wizard-file" multiple id="a8755cf0-f4d1-6376-ee21-a6defd1e7c08"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 37 37" xml:space="preserve"> <path class="circ path" style="fill:none;stroke:#77d27b;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:10;" d="M30.5,6.5L30.5,6.5c6.6,6.6,6.6,17.4,0,24l0,0c-6.6,6.6-17.4,6.6-24,0l0,0c-6.6-6.6-6.6-17.4,0-24l0,0C13.1-0.2,23.9-0.2,30.5,6.5z"></path> <polyline class="tick path" style="fill:none;stroke:#77d27b;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:10;" points="11.6,20 15.9,24.2 26.4,13.8 "></polyline> </svg> </div> </div> <div class="popover-container text-center"> <p data-toggle="popover" data-id="a8755cf0-f4d1-6376-ee21-a6defd1e7c08" class="btn-popover" data-original-title="" title=""> <span class="file-total-viewer">0</span> Files Selected <input type="button" value="view" href="javascript:void(0)" class="btn btn-success btn-xs btn-file-view"> </p> </div> </div>

Explanation

We have created a container named custom-file-picker .

. In this we have our custom file upload picture-container and our popover previewer popover-container .

and our popover previewer . Every file picker has unique id a8755cf0-f4d1-6376-ee21-a6defd1e7c08 and its corresponding popover refers to that id data-target="a8755cf0-f4d1-6376-ee21-a6defd1e7c08" to preview the files.

Styling our components

.picture-container{ position: relative; cursor: pointer; text-align: center; margin-bottom: 15px; } .picture{ width: 100px; height: 100px; background-color: #fff; color: #FFFFFF; margin: 5px auto; overflow: hidden; -webkit-transition: all 0.2s; -moz-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .picture > .icon{ width: 100%; height: 100%; display: inline-block; color: #37474F; border: 4px solid #CCCCCC; -webkit-border-radius: 50%; -moz-border-radius: 50%; -o-border-radius: 50%; border-radius: 50%; -webkit-transition: all 0.2s; -moz-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .picture:hover .icon{ border-color: greenyellow; } .picture > .icon > svg{ height: 1.4em; font-size: 4em; } .picture > svg{ width: 100%; height: 100%; } .picture input[type="file"]{ cursor: pointer; display: block; height: 100%; left: 0; opacity: 0 !important; position: absolute; top: 0; width: 100%; z-index: 100; right: 0; bottom: 0; } /*svg tick animation*/ .circ { opacity: 0; display: none; stroke-dasharray: 130; stroke-dashoffset: 130; -webkit-transition: all .75s; -moz-transition: all .75s; -ms-transition: all .75s; -o-transition: all .75s; transition: all .75s; } .tick{ stroke-dasharray: 50; stroke-dashoffset: 50; -webkit-transition: stroke-dashoffset .4s 0.5s ease-out; -moz-transition: stroke-dashoffset .4s 0.5s ease-out; -ms-transition: stroke-dashoffset .4s 0.5s ease-out; -o-transition: stroke-dashoffset .4s 0.5s ease-out; transition: stroke-dashoffset .4s 0.5s ease-out; } .drawn > svg .path{ display: block; opacity: 1; stroke-dashoffset: 0; } .drawn{ border-color: #fff; } [data-toggle="popover"]{ cursor: pointer; } span.popover-content-remove { padding-left: 10px; color: red; cursor: pointer; float: right; } .pb10{ padding-bottom: 10px; } .popover-header{ text-align: center; } .popover{ min-width: 200px; }

Handling the functionality

Now we have styled our components it is time to handle the functionality. We will use Jquery with ES6 to make things easy.

Storing the files

We will create a global variable to store the files.

//Global object to store the files let fileStorage = {};

We will use this variable to store all the files of the corresponding file picker with the help of its id.

Now we will create a function which will manage the storing of the file and displaying the count of the files. This function will take id and array of files as input.

//Store the file for particular filepicker let storeFile = (id, files) => { fileStorage[id] = files; //Update the file count $(`[data-id="${id}"] > .file-total-viewer`).text(files.length); }

$(`[data-id="${id}"] > .file-total-viewer`).text(files.length); will update the file count in popover previewer.

Handling the file picking

We have our function ready to update the count and store the files. We will just pass data to this function once the files are selected or changed.

$(document).ready(function(){ //Handle the file change $("input[type='file']").change(function(e){ //Get the id let id = e.target.id; //Get the files let files = e.target.files; //Store the file storeFile(id, files); //Show the complete icon $(this).siblings('.icon').hide(); $(this).parent().removeClass('drawn'); setTimeout(() => { $(this).parent().addClass('drawn'); }, 50); }); });

Once the files are selected we will show the complete animation with svg to notify users that files are changed.

Right now we have our file stored and count visible. Lets create the file previewer with bootstrap popover.

//Show file list $('[data-toggle="popover"]').popover({ html: true, title: "Files", placement:"bottom", content: function () { //Get the id of the file picker let id = $(this).attr('data-id'); //Get all the files of this filepicker let items = fileStorage[id]; //Preview the file let template = '<div class="row">'; if(items && items.length){ for(let val of items){ template += "<div class='col-12 pb10'><span class='popover-content-file-name'>" + val.name + "</span><span class='popover-content-remove' data-target='" + id + "' data-name='" + val.name + "' data-type='upload'><i class='fas fa-trash'></i></span></div>" } }else{ template += "<div class='col-12 pb10'><span class='popover-content'>No file found</span></div>"; } template += '</div>'; return template; } });

Bootstrap provides us a method to dynamically generate the content of the popover. So we attach the popover to [data-toggle="popover"] . Learn more about it here.

How it works

Every time a popup is about to render it will use it’s [data-target] id and pull all the files from the fileStorage .

id and pull all the files from the . If there are files then render those files along with the delete button.

If there is no file then show some message.

Now in case, you have multiple file uploader and you want only one popover to be open at a time when you add the following code.

//Prevent multiple popover $('body').on('click', function (e) { $('[data-toggle="popover"],[data-original-title]').each(function () { //the 'is' for buttons that trigger popups //the 'has' for icons within a button that triggers a popup if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) { (($(this).popover('hide').data('bs.popover') || {}).inState || {}).click = false; // fix for BS 3.3.6 } }); });

If you select some file and click on view you should be able to view it. Now the last thing we will do is handle the deletion of files.

Deleting the file

We have provided the id of the file picker to the delete button through data-target and name of the file through data-name . Every time delete icon is clicked we will use these values to remove the files.

//Delete files $(document).on('click', '.popover-content-remove', function (e) { //Get the id whose file to delete let id = $(this).attr('data-target'); //Get the name of the file to delete let name = $(this).attr('data-name'); //Confirm delete let isDelete = confirm("Do you really want to delete this file?"); //If confirmed if (isDelete) { //Remove the requested file let files = Object.values(fileStorage[id]); let newArr = files.filter((e) => { return e.name !== name; }); //Update the list storeFile(id, newArr); //If there is no file then show No file if(newArr.length === 0){ $(this).parent().parent().append("<div class='col-12 pb10'><span class='popover-content'>No file</span></div>"); } //Remove the current file $(this).parent().remove(); } });

As we are dynamically generating the content of the popover and its does not already exist in the DOM. We cannot assign a event to it. So we have to use a work around of assigning event on the DOM and checking if delete icon is clicked with $(document).on('click', '.popover-content-remove', function (e) {}); .

How it works

Once the delete icon is clicked we will ask for a confirmation from the user.

If user wants to proceed then we are fetching the id and the name assigned to delete button through data-target and data-name .

and . We are removing that particular file using filter() method.

Once the file is removed from the array then we are updating its count by passing the value to our helper function storeFile(id, newArr); .

. Also we remove the element from the popover. If the array is empty then show some message.

Note: You should provide a unique id to each file picker and its popover previewer.

Complete code

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Custom File picker</title> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"> <!-- Font Awesome --> <script defer src="https://use.fontawesome.com/releases/v5.7.2/js/all.js" integrity="sha384-0pzryjIRos8mFBWMzSSZApWtPl/5++eIfzYmTgBBmXYdhvxPc+XcFEk+zJwDgWbP" crossorigin="anonymous"></script> <!-- jQuery library --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <!-- Popper JS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script> <!-- Latest compiled JavaScript --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script> <style> .picture-container{ position: relative; cursor: pointer; text-align: center; margin-bottom: 15px; } .picture{ width: 100px; height: 100px; background-color: #fff; color: #FFFFFF; margin: 5px auto; overflow: hidden; -webkit-transition: all 0.2s; -moz-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .picture > .icon{ width: 100%; height: 100%; display: inline-block; color: #37474F; border: 4px solid #CCCCCC; -webkit-border-radius: 50%; -moz-border-radius: 50%; -o-border-radius: 50%; border-radius: 50%; -webkit-transition: all 0.2s; -moz-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .picture:hover .icon{ border-color: greenyellow; } .picture > .icon > svg{ height: 1.4em; font-size: 4em; } .picture > svg{ width: 100%; height: 100%; } .picture input[type="file"]{ cursor: pointer; display: block; height: 100%; left: 0; opacity: 0 !important; position: absolute; top: 0; width: 100%; z-index: 100; right: 0; bottom: 0; } /*svg tick animation*/ .circ { opacity: 0; display: none; stroke-dasharray: 130; stroke-dashoffset: 130; -webkit-transition: all .75s; -moz-transition: all .75s; -ms-transition: all .75s; -o-transition: all .75s; transition: all .75s; } .tick{ stroke-dasharray: 50; stroke-dashoffset: 50; -webkit-transition: stroke-dashoffset .4s 0.5s ease-out; -moz-transition: stroke-dashoffset .4s 0.5s ease-out; -ms-transition: stroke-dashoffset .4s 0.5s ease-out; -o-transition: stroke-dashoffset .4s 0.5s ease-out; transition: stroke-dashoffset .4s 0.5s ease-out; } .drawn > svg .path{ display: block; opacity: 1; stroke-dashoffset: 0; } .drawn{ border-color: #fff; } [data-toggle="popover"]{ cursor: pointer; } span.popover-content-remove { padding-left: 10px; color: red; cursor: pointer; float: right; } .pb10{ padding-bottom: 10px; } .popover-header{ text-align: center; } .popover{ min-width: 200px; } </style> </head> <body> <div class="custom-file-picker"> <div class="picture-container form-group"> <h4 class="info_text">Upload Proof of Physical Address</h4> <div class="picture"> <span class="icon"><i class="fas fa-file-upload"></i></span> <input type="file" class="wizard-file" multiple id="a8755cf0-f4d1-6376-ee21-a6defd1e7c08"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 37 37" xml:space="preserve"> <path class="circ path" style="fill:none;stroke:#77d27b;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:10;" d="M30.5,6.5L30.5,6.5c6.6,6.6,6.6,17.4,0,24l0,0c-6.6,6.6-17.4,6.6-24,0l0,0c-6.6-6.6-6.6-17.4,0-24l0,0C13.1-0.2,23.9-0.2,30.5,6.5z"></path> <polyline class="tick path" style="fill:none;stroke:#77d27b;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:10;" points="11.6,20 15.9,24.2 26.4,13.8 "></polyline> </svg> </div> </div> <div class="popover-container text-center"> <p data-toggle="popover" data-id="a8755cf0-f4d1-6376-ee21-a6defd1e7c08" class="btn-popover" data-original-title="" title=""> <span class="file-total-viewer">0</span> Files Selected <input type="button" value="view" href="javascript:void(0)" class="btn btn-success btn-xs btn-file-view"> </p> </div> </div> </body> <script> //Global object to store the files let fileStorage = {}; $(document).ready(function(){ $("input[type='file']").change(function(e){ //Get the id let id = e.target.id; //Get the files let files = e.target.files; //Store the file storeFile(id, files); //Show the complete icon $(this).siblings('.icon').hide(); $(this).parent().removeClass('drawn'); setTimeout(() => { $(this).parent().addClass('drawn'); }, 50); }); //Store the file for particular filepicker let storeFile = (id, files) => { fileStorage[id] = files; //Update the file count $(`[data-id="${id}"] > .file-total-viewer`).text(files.length); } //Show file list $('[data-toggle="popover"]').popover({ html: true, title: "Files", content: function () { //Get the id of the file picker let id = $(this).attr('data-id'); //Get all the files of this filepicker let items = fileStorage[id]; //Preview the file let template = '<div class="row">'; if(items && items.length){ for(let val of items){ template += "<div class='col-12 pb10'><span class='popover-content-file-name'>" + val.name + "</span><span class='popover-content-remove' data-target='" + id + "' data-name='" + val.name + "' data-type='upload'><i class='fas fa-trash'></i></span></div>" } }else{ template += "<div class='col-12 pb10'><span class='popover-content'>No file found</span></div>"; } template += '</div>'; return template; }, placement:"bottom" }); //Prevent multiple popover $('body').on('click', function (e) { $('[data-toggle="popover"],[data-original-title]').each(function () { //the 'is' for buttons that trigger popups //the 'has' for icons within a button that triggers a popup if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) { (($(this).popover('hide').data('bs.popover') || {}).inState || {}).click = false; // fix for BS 3.3.6 } }); }); //Delete files $(document).on('click', '.popover-content-remove', function (e) { //Get the id whose file to delete let id = $(this).attr('data-target'); //Get the name of the file to delete let name = $(this).attr('data-name'); //Confirm delete let isDelete = confirm("Do you really want to delete this file?"); //If confirmed if (isDelete) { //Remove the requested file let files = Object.values(fileStorage[id]); let newArr = files.filter((e) => { return e.name !== name; }); //Update the list storeFile(id, newArr); //If there is no file then show No file if(newArr.length === 0){ $(this).parent().parent().append("<div class='col-12 pb10'><span class='popover-content'>No file</span></div>"); } //Remove the current file $(this).parent().remove(); } }); }); </script> </html>