Guide on implementing a progress bar in JavaScript to track the download of a small file

Below is the function I created to handle an AJAX request for fetching data:

var xhr = createCORSRequest('GET', url);
if (!xhr) {
    alert('CORS not supported');
    return;
}
xhr.onload = function() {
    var txt1 = xhr.responsetxt1;
    var heading = getheading(txt1);

    if (heading == 'PASS') {
        var file = "My_URL" + ".js";
        downloadFile(file);
        //Need help with adding a progress bar here?
    } else {
        //Dealing with loading failure
    }
};

This is my downloadFile function for downloading files. However, I'm uncertain about how to:

  • Confirm if the download has finished.
  • Show a progress bar to indicate progress.

If you could provide some insight on how this can be achieved, that would be much appreciated. Thank you.

function downloadFile(fileName) {
    (function(d) {
        var ref = d.getElementsByTagName('script')[0];
        var js = d.createElement('script');
        js.src = fileName;
        ref.parentNode.insertBefore(js, ref);
        // Need assistance with displaying a progress bar here?
    }(document));
}

Answer №1

From my understanding, script elements do not provide progress events. The workaround is to use XHR to fetch the script's body and rely on browser cache for a subsequent download. However, there are no specific events for browser parsing of the script.

My solution is entirely in JS, making it adaptable to any framework. It assumes that downloading will take about 70% of the total time, with 20% allocated to browser parsing. I've used an unminified version of the impressive three.js 3D library as a sizable source file.

If the script is served from a different sandbox, progress calculation may be inaccurate. But if you serve your own script, this should not be an issue.

Keep in mind that this implementation is basic. For instance, I've used a simple HR element as a progress bar.

// This gives an estimate of file size for the example
let TOTAL_ESTIMATE = 1016 * 1024;
// Using hr as a 
let bar = document.getElementById("progressbar");
let button = document.getElementById("dlbtn");

var js; // to store the created DOM element
var fileName; // to keep the cacheBusted script address

/* This function will be called multiple times during the initial download, providing information on how much data has been loaded */

function onProgress(e) {
  var percentComplete = e.loaded / TOTAL_ESTIMATE;
  if (e.lengthComputable) {
    percentComplete = e.loaded / e.total;
  }
  p = Math.round(percentComplete * 100);
  console.log("progress", p + "%,", e.loaded, "bytes loaded")
  bar.style = "width: " + (5 + .6 * p) + "%"; // Assuming download will take around 60-70% of total time

}

/* This function is triggered when information is received. After the initial download completes, the readyState will be 4 and we set the src attribute of the file, triggering a re-download but taking advantage of the browser's cache. Although not ideal, simply eval-ing the data might yield better results. I assumed you wanted a <script> tag on your page that needed evaluation. */ 
function onReadyState(e) {
  let r = e.target;
  if (r.readyState != 4 || r.status != 200)
    return;
  let l = r.responseText.length;
  console.log("Success!", l, "total bytes (" + Math.round(l / 1024) + " KB)");
  bar.style = "width: 70%";
  
  js.src = fileName;
  bar.style = "width: 80%";
  var ref = document.getElementsByTagName('script')[0];
  ref.parentNode.insertBefore(js, ref);

};

// Called after the script has been evaluated:
function onScriptLoaded() {
  bar.style = "width: 100%; background-color: lightgreen;";
  button.disabled = false;
  console.log("Script evaluated?", THREE ? "yes" : "no"); // Demo file exposes window.THREE
}

function downloadFile(file) {
  button.disabled = true;
  (function(d) {
    // This part helps test the script multiple times. Remove in production
    fileName = file + "?bustCache=" + new Date().getTime();

    console.log("Inserting new script");
    js = d.createElement('script');
    js.type = "text/javascript";
    js.defer = "defer";
    js.async = "async";
    var r = new XMLHttpRequest();
    bar.style = "width: 5%"; // react immediately
    r.addEventListener("progress", onProgress);
    r.open("GET", fileName, true);
    r.onreadystatechange = onReadyState;
    js.onload = onScriptLoaded;
    r.send();
  }(document));
}
#progressbar {
  height: 6px;
  border-radius: 3px;
  width: 0%;
  border-color: green;
  background-color: green;
}
<button onclick="downloadFile('https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.js', this)" id="dlbtn">Download</button>
<script id="dummy" type="text/javascript">
  console.log("Dummy script ready")
</script>
<hr id="progressbar" align="left" />

Answer №2

IMPORTANT NOTE: Please be aware that I have not run a test on Android. This code snippet has only been tested on Chrome (desktop). You should be able to observe progress events being logged in the browser console.

var url = 'url/to/file/';
var request = new XMLHttpRequest();
request.responseType = "blob"; // Unsure if this is necessary
request.open("POST", url);

var self = this;
request.onreadystatechange = function () {
    if (request.readyState === 4) {
        var file = $(self).data('file');            
        var anchor = document.createElement('a');
        anchor.download = file;
        anchor.href = window.URL.createObjectURL(request.response);
        anchor.click();            
    }
};

request.addEventListener("progress", function (e) {
    if(e.lengthComputable) {
        var completedPercentage = e.loaded / e.total;
        console.log("Completed: ", completedPercentage , "%");
    }
}, false);
request.send();

I trust this information proves useful.

Answer №3

The effectiveness greatly relies on the programming language rather than the client side. Take PHP for instance, which offers a session upload progress feature: http://php.net/manual/en/session.upload-progress.php

Regarding the client side, Boris' response serves as a valuable example. Trust it provides assistance.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best practice for creating fragments - in the viewModel or in the Activity?

I'm currently in the process of learning about the MVVM pattern and have been working on an example project to practice. However, I'm facing a dilemma regarding whether fragments should be created in the ViewModel or in the Activity. Initially, ...

"Encountering Cross-Origin Resource Sharing problem when making an Ajax call to Wiki API in

I'm currently experimenting with an API endpoint I found on Wikipedia: https://en.wikipedia.org/w/api.php?action=query&titles=Main%20Page&prop=revisions&rvprop=content&format=json To make this work in my Ruby on Rails project, I am u ...

Using jQuery to find a specific value within a string and deleting everything before the <li> tags

Within this snippet of code, there is a block of text categorized as a string due to ACF's functionality: <div class="acf-fc-popup"><ul><li><a href="#" data-layout="two_column_layout" data-min="0" data-max="0">Two Column Layou ...

Is it safe to depend on the attachment order when managing events?

From what I recall, in the pure W3C event model (using addEventListener), there is no guarantee that event handlers will be invoked in the order they have been attached. But what about jQuery's event model? Is it safe to assume the order of attaching ...

Is it possible to search a JSON Array using a variable as the criteria

I have a JSON Array that needs to be searched: [ { "Device_ID":"1", "Image":"HTC-One-X.png", "Manufacturer":"HTC", "Model":"One X", "Region":"GSM", "Type":"Phone" }, { "Device_ID":"2", "Image":"Motorol ...

JavaScript animation text not activating

I am working on a Bootstrap 5 carousel that displays two lines of caption per slide. I want to create an animation where the old captions fade out and move away as the slide changes, and the new captions fade in and move into place on the next slide. Howev ...

Using a custom ParcelJS plugin from the local directory

I am interested in developing a plugin to enable raw loading of a specific type of file with Parcel. According to the documentation, Parcel detects plugins published on npm with certain prefixes and automatically loads them along with their dependencies li ...

Exploring Two Arrays in JavaScript Using GSAP

I'm struggling with iterating through two arrays in order to adjust my gsap timeline accordingly. const subTabs = Array.from(document.querySelectorAll(".subtab")); const expandTabs = Array.from(document.querySelectorAll(".expandtab" ...

How can I preserve the line break in a textarea using PHP?

Is it possible to maintain line breaks in a textarea using PHP? Currently, I have a temporary solution that involves using the exec function to run a shell command, but I would prefer a purely PHP approach. Below is my temporary script - can you help me mo ...

Kendo Grid custom pop-up editor template featuring a unique jQuery Accordion Wizard

I am encountering an issue with executing the jQuery code inside the Kendo Grid Editor popup. I have attempted to incorporate this feature: While I can successfully display the accordion within the edit window popup, the functionality is not working as e ...

How to Implement Filtering in AngularJS ng-repeat

While developing my app, I came across a challenge with the filter in my ng-repeat. Initially, I could search by productCode or name. Then, a new requirement emerged - "Filter by SubcategoryId." The issue lies in filtering strictly by subCategoryId, while ...

Execute an if statement in PHP upon clicking an image, all while avoiding the need to refresh the

My goal is to trigger my PHP mail(); code with an if statement when an image is clicked. Here's what I've attempted so far: <?php $to = "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="720013041c111d1f02131c0b321 ...

Control an Android device from a distance

I am embarking on the exciting journey of developing an Airdroid-inspired app. I understand that this project will pose a significant challenge, but I firmly believe in the philosophy that the best way to learn is by pushing oneself to new limits. The con ...

iOS application receives a 430 forbidden error when handling Ajax response in Cordova

I've been working on creating a SAPUI5 Application and encountered an issue when trying to run it on the cordova iOS platform. I keep receiving a 403 forbidden response from the server when sending a jQuery ajax request. Interestingly, the service wor ...

A more efficient method for importing numerous 'export' statements using ES6 or TypeScript

Currently, I am utilizing D3.js V4 with the module and my goal is to import multiple modules into a singular namespace. The code snippet provided below showcases my current approach, but I am curious if there is a more efficient method available. const d3 ...

Creating dynamic TextBox fields in JSP based on the selected option from a dropdown menu fetched from the database

Hello, I need some assistance in creating dependent Textboxes based on Dropdown values fetched from a database using Jsp. The code provided below is working fine for one Textbox, but I am unsure how to extend it for multiple dependent Textboxes. Any help w ...

Struggling with EditText: Acting as Watcher for the View and Observer for the MutableLiveData

I'm struggling to grasp the concept of using the Fragment + ViewModel pattern with a View like an EditText. Since the EditText will be modified within the Fragment, how can I also update it from the ViewModel, for example, to clear its text? Below i ...

What is the best way to enable swipe functionality for ion-items in Ionic without requiring a click?

I have been working on implementing an ion-list with swipable ion-items that do not need to be clicked on the side to trigger an event. The functionality I am aiming for is similar to the default contacts app on Samsung phones, where a left swipe calls an ...

What is preventing the angular.bootstrap code from functioning properly in AngularJS versions prior to V1.6?

Can someone explain why when using the URL http://code.angularjs.org/snapshot/angular.js everything works fine in my code within the <script> tag, but when I switch to https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js, Angular does ...

Error encountered in Angular2 code due to a problem with inline templates

I'm grappling with the code below: import { Component } from '@angular/core'; @Component({ selector: 'character', template: `<h2 class="{{name}}">{{name}}</h2> <ul> ...