How to create a file upload widget with progress tracker for each file using Vanilla JavaScript
Bishwajeet Pandey
Versatile Frontend Developer | Next Js, Vue Js, Angular 14, Flutter, React Native, Babylon.js, Three.js | Specialized in Crafting Frontend Experiences for Web and Mobile Apps
Uploading files to a server is a fundamental part of web development, and it’s crucial to ensure that the user has a good?experience when doing so. In this article, i will explain how to build an upload widget with a progress tracker for each file?in vanilla JavaScript.
Creating the HTML Markup
Let’s start by creating the HTML markup for our upload widget. We’ll need a container element to hold our widget, a row element for the file upload area, and a task container to display the list of files that are being uploaded. The HTML code will look like this:
? ? <div class="container"
? ? ? <div class="main">
? ? ? ? <div class="row">
? ? ? ? ? <div class="instruction">
? ? ? ? ? ? <span class="material-symbols-outlined upload-icon">
? ? ? ? ? ? ? cloud_upload
? ? ? ? ? ? </span>
? ? ? ? ? ? <div class="text_for_icon">Select the files</div>
? ? ? ? ? </div>
? ? ? ? </div>
? ? ? ? <div class="task_container"></div>
? ? ? </div>
? ? </div>>
The HTML structure of the upload widget is simple. It consists of a container element with a class of "container". Inside the container, there is a main element with a class of "main". The main element contains two child elements:
- An element with a class of "row" that serves as an upload button.
- An element with a class of "task_container" that displays the progress of each file being uploaded.
CSS Styling
The CSS styling is used to give the upload widget a modern look and feel. It is responsible for the scroll bar customization, the background color of the widget, the shadow effect, and the overall layout of the widget.
The styling also includes classes for the upload button, the progress tracker, and the file information that is displayed during the upload process. The upload button is styled as a green rectangle with a white upload icon, while the progress tracker is styled as a circular progress bar that indicates the progress of each file being uploaded
The output will look like this :
* {
? margin: 0;
? padding: 0;
? box-sizing: border-box;
? font-family: "Poppins";
}
/*code for scrool bar customization*/
::-webkit-scrollbar {
? width: 7px;
}
::-webkit-scrollbar-track {
? background: #212121;
? box-shadow: 1px 2px 7px 3px #212121 inset;
}
::-webkit-scrollbar-thumb {
? background: rgb(88, 88, 88);
? border-radius: 100vw;
? height: 70px;
}
::-webkit-scrollbar:hover {
? width: 5px;
}
::-moz-scrollbar:hover {
? width: 5px;
}
@supports (scrollbar-color: rgb(88, 88, 88) #212121) {
? * {
? ? scrollbar-color: rgb(88, 88, 88) #212121;
? ? scrollbar-width: thin;
? }
}
.container {
? height: 100vh;
? background-color: #befddd;
? display: flex;
? align-items: center;
? justify-content: center;
}
.main {
? width: 100%;
? background-color: #15f37d;
? box-shadow: rgba(0, 0, 0, 0.56) 0px 22px 70px 4px;
}
.row {
? height: 20vh;
? display: flex;
? justify-content: center;
? align-items: center;
? cursor: pointer;
}
.instruction {
? display: flex;
? flex-direction: column;
? align-items: center;
? font-size: 1.1rem;
? font-family: "Poppins", sans-serif;
}
.upload-icon {
? color: rgb(15, 15, 15);
? font-size: 2.2rem;
? font-weight: bold;
}
.task_container {
? max-height: 50vh;
? overflow-y: auto;
? background-color: #1a1f1d;
}
.task_wrapper {
? margin-bottom: 0.7rem;
? margin-left: 0.7rem;
? margin-right: 0.7rem;
? display: flex;
? padding: 5px;
}
.task_wrapper:first-child {
? margin-top: 1rem;
}
.task_wrapper:last-child {
? margin-bottom: 0rem;
}
.file_container {
? display: flex;
? align-items: center;
}
.file_info_wrapper {
? width: calc(100% - 2.9rem);
}
.file_icon_wrapper {
? display: flex;
? justify-content: center;
}
.file_icon_wrapper img {
? width: 100%;
? height: 100%;
}
.uploading_info_txt {
? color: #f3f3f3;
? margin-top: 5px;
? font-size: 0.9rem;
}
.file_name {
? text-overflow: ellipsis;
? white-space: nowrap;
? align-items: center;
? overflow: hidden;
? color: rgb(191, 233, 212);
}
.progress_container {
? width: 2.9rem;
? display: flex;
? justify-content: end;
? align-items: center;
}
.progress_tracker_outer {
? height: 2rem;
? width: 2rem;
? background-image: conic-gradient(
? ? rgba(83, 81, 81, 0.521) 80%,
? ? rgba(87, 84, 84, 0.459) 0deg
? );
? border-radius: 50%;
? display: grid;
? place-items: center;
}
.inner_circle {
? height: 1.5rem;
? width: 1.5rem;
? border-radius: 50%;
? background-color: #1a1f1d;
? display: grid;
? place-items: center;
}
.loader {
? border: 0.5rem solid #f3f3f3;
? border-radius: 50%;
? border-top: 0.5rem solid #15f34c;
? -webkit-animation: spin 2s linear infinite; /* Safari */
? animation: spin 1s linear infinite;
}
.responseSussesCircle {
? border: 0.5rem solid #15f34c;
}
/* Safari */
@-webkit-keyframes spin {
? 0% {
? ? -webkit-transform: rotate(0deg);
? }
? 100% {
? ? -webkit-transform: rotate(360deg);
? }
}
@keyframes spin {
? 0% {
? ? transform: rotate(0deg);
? }
? 100% {
? ? transform: rotate(360deg);
? }
}
.abort-btn,
.uploadCancelIcon,
.doneIcon {
? font-size: 1.3rem;
? font-weight: bold;
}
.abort-btn {
? color: #fff;
? font-size: 1rem;
? cursor: pointer;
}
.uploadCancelIcon {
? color: rgb(236, 19, 19);
}
.doneIcon {
? color: #15f34c;
}
/*designing file icon from scratch*/
/*file icon ourter box*/
.box_1 {
? height: 100%;
? width: 100%;
? position: relative;
? overflow: hidden;
? border: 2px solid rgb(224, 11, 11);
? border-radius: 4px;
? background-color: #2d3531;
}
/*file icon extension label-box*/
.ext_name {
? color: #fff;
? background-color: rgb(224, 11, 11);
? font-weight: bold;
? font-size: 0.7rem;
? position: absolute;
? width: 100%;
? top: 52%;
? left: -5px;
? transform: translate(0, -50%);
? text-align: center;
? box-shadow: 1px 1px 3px #969696;
? border-radius: 2px;
}
/* file icon folded triangle from top right corner */
.triangle {
? position: absolute;
? height: 18px;
? width: 15px;
? bottom: 0px;
? top: -8px;
? right: -7px;
? transform: rotate(-45deg);
? background-color: rgb(224, 11, 11);
? border: 1px solid rgb(255, 255, 255);
? box-shadow: rgb(192, 192, 192) 0px 3px 6px, rgb(66, 64, 64) 0px 3px 6px;
}
/*extra extra small*/
@media screen and (min-width: 0px) {
? .main {
? ? height: auto;
? ? width: calc(100% - 10px);
? }
? .loader {
? ? border: 2px solid #f3f3f3;
? ? border-top: 2px solid #15f34c;
? }
? .responseSussesCircle {
? ? border: 2px solid #15f34c;
? }
? .file_info_wrapper {
? ? width: calc(100% - 4.6rem); /*2.3+2.3 rem*/
? }
? .progress_container,
? .file_icon_wrapper {
? ? width: 2.3rem;
? }
? .file_icon_wrapper {
? ? height: 2.7rem;
? }
? .file_icon_container {
? ? display: flex;
? ? align-items: center;
? }
? .file_info_wrapper {
? ? padding: 0px 5px;
? }
} /*extra small*/
@media screen and (min-width: 500px) {
? .main {
? ? max-height: 380px;
? ? width: 480px;
? }
? .file_info_wrapper {
? ? padding: 0px 1rem;
? }
}
/*small*/
@media screen and (min-width: 576px) {
? .main {
? ? max-height: 430px;
? ? width: 530px;
? }
? .file_info_wrapper {
? ? padding: 0px 1.5rem;
? }
}
/*medium*/
@media screen and (min-width: 768px) {
? .main {
? ? max-height: 450px;
? ? width: 550px;
? }
}
/*large*/
@media screen and (min-width: 993px) {
? .main {
? ? max-height: 500px;
? ? width: 600px;
? }
}
/*extra large*/
@media screen and (min-width: 1300px) {
? .main {
? ? max-height: 600px;
? ? width: 700px;
? }
}
/*break point for height*/
@media screen and (max-height: 320px) {
? .main {
? ? max-height: none;
? ? height: 100vh;
? ? width: 100%;
? }
? .task_container {
? ? max-height: 80vh;
? ? overflow-y: auto;
? }
}
JavaScript Code
The JavaScript code for the upload widget is responsible for the functionality of the widget. It includes an event listener for the upload button that triggers the upload process when the user selects one or more files. The code also tracks the progress of each file being uploaded and displays it in real-time to the user.
Let's take a closer look at the JavaScript code and understand how it works.
Defining query selector function
The _ function simply returns the result of calling querySelector with the specified element argument.
function _(element)
? return document.querySelector(element);
}
Defining idGenerator function
The generator function is a powerful tool for generating unique IDs in JavaScript. By using a generator function, you can easily generate unique IDs for each new request, and keep track of each UI element related to a particular request. This can help you to better manage and track your data, and make your code more efficient and robust.
The idGenerator() function contains a while loop that runs indefinitely. Inside the loop, the yield keyword is used to return the current value of the count variable. The yield keyword temporarily pauses the execution of the function and allows the caller to retrieve the current value.
The count variable is initialized to -1, which means that the first ID generated by the generator function will be 0. After the first ID is generated, the count variable is incremented by 1 using the ++ operator.
The generator function is called by creating an instance of the generator object using the idGenerator() function, like this: let generatorObj = idGenerator();. Once the generator object has been created, it can be used to generate unique IDs by calling the next() method on the object, like this: let id = generatorObj.next().value;
function* idGenerator()
? let count = -1;
? while (true) yield ++count;
}
let generatorObj = idGenerator();
File Input Event Listener
The first step in creating the upload widget is to create an event listener for the file input element. This listener will trigger the upload process when the user selects one or more files.
_(".row").addEventListener("click", () =>
? let input = document.createElement("input");
? input.type = "file";
? input.setAttribute("multiple", true);
? input.onchange = (e) => {
? ? const fileList = input.files;
? ? for (const file of fileList) {
? ? ? if (fileList.size === 0) continue;
? ? /* ? ? ?
? ? here we can apply different validations on each selected file
? ? by using different property of file obj like file.
? ? file.type
? ? file.size e.t.c
? ? */
? ? ? let requestId = generatorObj.next().value;
? ? ? createProgressTrackers(file, requestId); //create ui elements for each request
? ? ? sendRequest(file, requestId); //use to send unique request for each file
? ? }
? };
? input.click();
});
This code snippet is responsible for handling the event of clicking on an HTML element with the class "row". When this element is clicked, a file upload dialog is opened, and when the user selects one or multiple files, a loop is executed to handle each file individually.
Within this loop, various validations can be performed on each selected file . The next step involves generating a unique ID for each file request, using the idGenerator() function.
The createProgressTrackers() function is then called to create dynamic UI elements for each file request, such as ciruclar progress bars or file names and the sendRequest() function is called to send the unique request for each file.
Overall, this code is responsible for handling the user input of selecting files for upload and generating unique xhr requests for each file. It also creates UI elements to track the progress and response coming from server for each upload request.
Storing file icon colors for quick lookup
const fileIconsColors =
? ai: "#ff5e00",
? avi: "#06c941",
? doc: "#ddf315",
? gif: "#c715f3",
? jpg: "#f3158f",
? mkv: "#c90661",
? mp3: "#15f3bb",
? mp4: "#15f34c",
? pdf: "#15b4f3",
? ppt: "#1549f3",
? exe: "#f462fe",
? log: "#fefefe",
? psd: "#7515f3",
? png: "#7613f3",
? svg: "#f36615 ",
? txt: "#f3a515",
? xls: "#b0f315",
? zip: "#4c0391",
? default: "#f31515",
};
//you can store many more icon and it's color as a name value pair in this
//object
This object is related to the visual aspect of the project.
The purpose of this object is to allow the system to quickly search for the color associated with an uploaded file in constant time and if the icon is not present, it shows the default icon with a specific color
Each key-value pair represents a unique file extension and the color to be used for its associated icon. This object allows for quick and easy lookup of the correct color based on the file extension of the uploaded file
Creating Progress Tracker for File Uploads
The function define here create dynamic elements for each selected file.
领英推è
through these elements user will have various information related to each selected file along with these user can track upload progress and response in real time and may abort the request in between.
function createProgressTrackers(file, requestId)
? //finding the extension of file through file name
? let fileExt = file.name
? ? .split(".")
? ? .slice(-1)
? ? .join()
? ? .substring(0, 3)
? ? .toUpperCase();
? // Create the main task wrapper element
? const taskWrapper = document.createElement("div");
? taskWrapper.classList.add("task_wrapper");
? // Create the file icon container element and append it to the task wrapper
? const fileIconContainer = document.createElement("div");
? fileIconContainer.classList.add("file_icon_container");
? taskWrapper.appendChild(fileIconContainer);
? // Create the file icon wrapper element and append it to the file icon container
? const fileIconWrapper = document.createElement("div");
? fileIconWrapper.classList.add("file_icon_wrapper");
? fileIconContainer.appendChild(fileIconWrapper);
? // Create the image element and set its source to the provided icon name, and append it to the file icon wrapper
? const box1 = document.createElement("div");
? box1.classList.add("box_1");
? // ? image.setAttribute("src", `files_icon/${fileExt}.png`);
? box1.classList.add("box_1");
? const extName = document.createElement("div");
? extName.classList.add("ext_name");
? extName.textContent = fileExt;
? box1.appendChild(extName);
? const triangle = document.createElement("div");
? triangle.classList.add("triangle");
? box1.appendChild(triangle);
? fileIconWrapper.appendChild(box1);
? /*coloring the file icons*/
? let color = fileIconsColors[fileExt.toLowerCase()];
? color = color ?? fileIconsColors.default;
? box1.style.border = `2px solid ${color}`;
? extName.style.backgroundColor = color;
? triangle.style.backgroundColor = color;
? const fileInfoWrapper = document.createElement("div");
? fileInfoWrapper.classList.add("file_info_wrapper");
? taskWrapper.appendChild(fileInfoWrapper);
? const fileName = document.createElement("div");
? fileName.classList.add("file_name");
? fileName.textContent = file.name;
? fileInfoWrapper.appendChild(fileName);
? const uploadingInfoTxt = document.createElement("div");
? uploadingInfoTxt.classList.add("uploading_info_txt");
? uploadingInfoTxt.textContent = "";
? uploadingInfoTxt.id = "icon_id" + requestId;
? fileInfoWrapper.appendChild(uploadingInfoTxt);
? const progressContainer = document.createElement("div");
? progressContainer.classList.add("progress_container");
? taskWrapper.appendChild(progressContainer);
? const progressTrackerOuter = document.createElement("div");
? progressTrackerOuter.classList.add("progress_tracker_outer");
? progressTrackerOuter.id = "req" + requestId;
? progressContainer.appendChild(progressTrackerOuter);
? const innerCircle = document.createElement("div");
? innerCircle.classList.add("inner_circle");
? progressTrackerOuter.appendChild(innerCircle);
? const abortBtn = document.createElement("span");
? abortBtn.classList.add("material-symbols-outlined", "abort-btn");
? abortBtn.textContent = "close";
//attaching the click event on abort button so that request cab be
//aborted in between
? abortBtn.onclick = function () {
? ? abort(requestId);
? };
? innerCircle.appendChild(abortBtn);
? const parentElement = document.querySelector(".task_container");
? parentElement.insertBefore(taskWrapper, parentElement.children[0]);
}
When the function is called, it takes in two parameters: file which is an object that contains the file name, size and type, and requestId which is a unique identifier for each file upload request.
The function then creates several HTML elements for displaying the progress tracker. It begins by finding the file extension from the file name and then creates a task wrapper element with the class "task_wrapper". Inside the task wrapper element, it creates a file icon container element, which contains a file icon wrapper element with a file icon image and the file extension name. The file icon image is determined based on the file extension and its color is based on the fileIconsColors object.
The function then creates a file info wrapper element containing the file name, and an empty uploadingInfoTxt element with a unique identifier based on the requestId. Lastly, it creates a progress container element, a progress tracker outer element, an inner circle element, and an abort button.
All of the created HTML elements are then appended to the parent element with the class "task_container".
Uploading files using AJAX request with progress and response tracking
This code snippet defines the sendRequest function which is responsible for sending a file to the server via AJAX request using XMLHttpRequest object. It takes two parameters - the file to be uploaded and a unique requestId.
let requestList = [];//defining array to store the ref. of each xhr req.
function sendRequest(file, requestId) {
? const formData = new FormData();
? formData.append("file", file, "myFile");
? let xhr = new XMLHttpRequest();
? xhr.responseType = "json";
? xhr.onload = () => {
? ? //change the logo to completed sign
? ? console.log(xhr.response);
? ? if (xhr.status === 200 || xhr.status === 201) {
? ? ? /*if response successfully received
? ? ? then stop the loader and change the icon
? ? ? ?color to show the successfully completion */
? ? ? removeLoader(requestId);
? ? }
? };
? xhr.upload.onprogress = (e) => {
? ? let { loaded, total } = e;
? ? let percent = Math.floor((loaded / total) * 100);
? ? //updating the circular loader to total uploaded data
? ? _("#req" + requestId).style.backgroundImage =
`conic-gradient(
? ? ? ? #15f34c ${percent}%,
? ? ? ? rgba(228, 228, 228, 0.719) 0deg
? ? ? )`;
? ? if (percent === 100) {
? ? ? //if 100% data uploaded then waiting for response from the server
? ? ? //so ?changing the progress tracker to loading mode
? ? ? changeProgressTracker(requestId);
? ? }
? ? //updating uploading info text
? ? _("#icon_id" + requestId).innerText =
`${(loaded / 1024 / 1024).toFixed( 2)} MB /
${(total / 1024 / 1024).toFixed(2)} MB (${percent}%)`;
? };
? xhr.open("post", "upload.php", true);
? xhr.send(formData);
? /*saving each xhr object ref. in the array so that user can abort it later if they
? wish to do so*/
? requestList.push(xhr);
}
The function creates a new FormData object and appends the file to it. It then creates a new XMLHttpRequest object, sets its responseType property to "json", and attach two event handlers to xhr object.
(A).The onload event handler :
The onload event handler is called when the response has been successfully received. If the response status is 200 or 201, the function calls the removeLoader function to stop the loading indicator and change the icon color to indicate successful completion.
(B)The xhr.upload.onprogress :
This event handler is called as the upload progresses and is used to track the progress of the upload. It updates the circular loader to show the progress of the upload, changes the progress tracker to loading mode if the upload is complete, and updates the uploading info text to show the current progress.
The function then opens the request with the "POST" method and the URL of the server-side script that handles the upload. Finally, it sends the form data to the server.
The requestList array is used to store references to each XMLHttpRequest object that is created, so that the user can abort the upload if they wish to do so.
Overall, this function is responsible for sending the file to the server via AJAX request and updating the UI to show the progress of the upload.
Aborting an Upload Request
function abort(requestId)
? let xhr = requestList[requestId];
? //checking if upload is already done or not
? if (xhr.readyState > 1) {
? ? alert(" opps! it's too late to cancel the upload");
? ? return;
? }
? xhr.abort();
? /*fetching how much percent data is uploaded through css property*/
? let conicGradientStr = _("#req" + requestId).style.backgroundImage;
? let endIndexOfSubStr = conicGradientStr.indexOf("%,");
? let startIndexOfSubStr = endIndexOfSubStr - 2;
? let uploadedPercentage = conicGradientStr.substring(
? ? startIndexOfSubStr,
? ? endIndexOfSubStr
? );
? //changing the color of conic-gradient to red for showing aborted request
? _("#req" + requestId).style.backgroundImage = `conic-gradient(
? ? rgb(236, 19, 19) ${uploadedPercentage}%,
? ? rgba(228, 228, 228, 0.719) 0deg
? )`;
? let iconElement = _("#req" + requestId).firstElementChild.firstElementChild;
? iconElement.className = "material-symbols-outlined uploadCancelIcon";
? iconElement.innerText = "file_upload_off";
}
When the user clicks on the "cancel" icon/button for an ongoing upload request, this function gets called. It takes in a requestId parameter to identify the upload request that needs to be canceled.
First, it checks if the upload is already completed or not by checking the readyState property of the XMLHttpRequest object. If the readyState is greater than 1, it means the upload is already completed, and the function displays an alert message informing the user that it's too late to cancel the upload.
If the upload is still in progress, the function calls the abort method on the XMLHttpRequest object to abort the upload request.
Next, it fetches the percentage of uploaded data from the CSS property of the circular loader element and updates the background color of the loader to red to show that the upload request was canceled.
Finally, it updates the cancel icon to indicate that the upload request was canceled. It changes the icon to the "file_upload_off" symbol and updates the CSS class of the icon to "material-symbols-outlined uploadCancelIcon".
Overall, this function provides the functionality to cancel an ongoing upload request and gives visual feedback to the user to show that the request was canceled.
Updating UI of circular Progress Tracker on Upload Completion
On the completion of uploading file the circular Progress Tracker will be changed
to ciruclar loader which indicate that the client is waiting for the response from the server for that uploaded request .
function changeProgressTracker(requestId)
? /*this loader will be activated only when the 100% data uploaded
? ? from the client side and client is waiting for the response from the
? ? server */
? console.log("100% data uploaded");
? const progressTracker = _("#req" + requestId);
? //remove the conic gradient style
? progressTracker.style.backgroundImage = "";
? //add the loader class to progress tracker
? progressTracker.classList.add("loader");
? const childElement = progressTracker.firstElementChild;
? childElement.parentElement.removeChild(childElement);
}
The function changeProgressTracker(requestId) is called when the upload of a file is completed and the client is waiting for the server response.
The function first removes the background-image style from the progress tracker, which was used to show the progress of the file upload. Then it adds the class loader to the progress tracker, which activates a loading animation to indicate that the client is waiting for the server response.
Finally, the function removes the child element of the progress tracker, which was previously used to show the progress percentage of the file upload.
Removing Circular Loader
Here i defines a function that removes the loader and updates the progress tracker to show the completion of the task.
The function takes in one parameter "requestId", which is used to get the progress tracker element. It removes the "loader" class from the progress tracker and adds the "responseSussesCircle" class. It then creates a span element with a "doneIcon" class and adds the text "done" to it. This span element is then appended to the progress tracker element.
Overall, this function is responsible for updating the UI to reflect the successful completion of the upload process.
function removeLoader(requestId)
? /*this function invocation will cancel out the ui update of
? ?changeProgressTracker function and modify the progress tracker
? ?to show the completion of task*/
? const progressTracker = _("#req" + requestId);
? progressTracker.classList.remove("loader");
? progressTracker.classList.add("responseSussesCircle");
? const span = document.createElement("span");
? span.className = "material-symbols-outlined doneIcon";
? span.appendChild(document.createTextNode("done"));
? progressTracker.appendChild(span);
}
------END------
Repo Link :
Live Page Link:
MCA Student || KL University
2 å¹´Great work bro ??
Software Engineer | Data Analyst | B.Tech in Computer Science | Tech Enthusiastic | Learner | Explorer
2 å¹´Great value