/upload_widget

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 project, i had created an upload widget with a progress tracker for each file in vanilla JavaScript.

Primary LanguageJavaScript

upload_widget

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 project, i had created an upload widget with a progress tracker for each file in vanilla JavaScript.

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

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------