15

I am trying to download files using Ajax and show a custom download progress bar.

The problem is I can't understand how to do so. I wrote the code to log the progress but don't know how to initiate the download.

NOTE: The files are of different types.

Thanks in advance.

JS

// Downloading of files
filelist.on('click', '.download_link', function(e){
    e.preventDefault();
    var id = $(this).data('id');
    $(this).parent().addClass("download_start");

    $.ajax({
        xhr: function () {
            var xhr = new window.XMLHttpRequest();
            // Handle Download Progress
            xhr.addEventListener("progress", function (evt) {
                if(evt.lengthComputable) {
                    var percentComplete = evt.loaded / evt.total;
                    console.log(percentComplete);
                }
            }, false);
            return xhr;
        },
        complete: function () {
            console.log("Request finished");
        }
    })
});

HTML and PHP

    <li>
    <div class="f_icon"><img src="' . $ico_path . '"></div>
    <div class="left_wing">
       <div class="progressbar"></div>
       <a class="download_link" href="#" id="'.$file_id.'"><div class="f_name">' . $full_file_name . '</div></a>
       <div class="f_time_size">' . date("M d, Y", $file_upload_time) . '&nbsp; &#149; &nbsp;' . human_filesize($file_size) . '</div>
    </div>

    <div class="right_wing">
       <div class="f_delete">
       <a class="btn btn-danger" href="#" aria-label="Delete" data-id="'.$file_id.'" data-filename="'.$full_file_name.'"><i class="fa fa-trash-o fa-lg" aria-hidden="true" title="Delete this?"></i>
       </a>
    </div>
   </div>
    </li>
Dekel
  • 60,707
  • 10
  • 101
  • 129
Ayan
  • 2,738
  • 3
  • 35
  • 76
  • How are you sending the file you've downloaded to the user? – freedomn-m Sep 20 '16 at 09:13
  • If you are asking how am I downloading file, well that is what I want to know how should I download the file. – Ayan Sep 20 '16 at 09:15
  • It looked like you were asking how to show the progress, but on second read, this is an extra to the download. Simple answer: don't use ajax to download a file, use the browser. `download` – freedomn-m Sep 20 '16 at 09:42
  • Yes that can be obviously done but I need to show the download progress as it is the part of the UI – Ayan Sep 20 '16 at 09:44
  • There's an answer here http://stackoverflow.com/questions/399641/ajax-page-download-progress but a better discussion here http://stackoverflow.com/questions/76976/how-to-get-progress-from-xmlhttprequest – freedomn-m Sep 20 '16 at 10:46
  • @freedomn-m the first post is quite old but I went through the community wiki. But still I can't join the pieces of how to get it to work. – Ayan Sep 20 '16 at 10:57
  • I'd worry about how to get the download prompted to the user first, before adding a progress bar. General consensus is that it's not possible because that's now how ajax works. eg: http://stackoverflow.com/questions/20830309/download-file-using-an-ajax-request and http://stackoverflow.com/questions/35000500/send-radflowdocument-as-pdf-to-user/35000901#35000901 hence my first question in comments :) – freedomn-m Sep 20 '16 at 12:06
  • Well, I have a list of files on a page with blank anchor tags wrapped arounf their filenames. On click of the filenames the background of the filename gets set to a different colour, which I use to show the progress.(Not sure if I got what you mean). I will update the question with the html. – Ayan Sep 20 '16 at 12:11

1 Answers1

30

If you want to show the user a progress-bar of the downloading process - you must do the download within the xmlhttprequest. One of the problems here is that if your files are big - they will be saved in the memory of the browser before the browser will write them to the disk (when using the regular download files are being saved directly to the disk, which saves a lot of memory on big files).

Another important thing to note - in order for the lengthComputable to be true - your server must send the Content-Length header with the size of the file.

Here is the javascript code:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="a1" data-filename="filename.xml">Click to download</div>
<script>
$('#a1').click(function() {
    var that = this;
    var page_url = 'download.php';

    var req = new XMLHttpRequest();
    req.open("POST", page_url, true);
    req.addEventListener("progress", function (evt) {
        if(evt.lengthComputable) {
            var percentComplete = evt.loaded / evt.total;
            console.log(percentComplete);
        }
    }, false);

    req.responseType = "blob";
    req.onreadystatechange = function () {
        if (req.readyState === 4 && req.status === 200) {
            var filename = $(that).data('filename');
            if (typeof window.chrome !== 'undefined') {
                // Chrome version
                var link = document.createElement('a');
                link.href = window.URL.createObjectURL(req.response);
                link.download = filename;
                link.click();
            } else if (typeof window.navigator.msSaveBlob !== 'undefined') {
                // IE version
                var blob = new Blob([req.response], { type: 'application/force-download' });
                window.navigator.msSaveBlob(blob, filename);
            } else {
                // Firefox version
                var file = new File([req.response], filename, { type: 'application/force-download' });
                window.open(URL.createObjectURL(file));
            }
        }
    };
    req.send();
});
</script>

And here is an example for the php code you can use:

<?php
$filename = "some-big-file";
$filesize = filesize($filename);

header("Content-Transfer-Encoding: Binary");
header("Content-Length:". $filesize);
header("Content-Disposition: attachment");

$handle = fopen($filename, "rb");
if (FALSE === $handle) {
    exit("Failed to open stream to URL");
}

while (!feof($handle)) {
    echo fread($handle, 1024*1024*10);
    sleep(3);
}

fclose($handle);

Note that I added a sleep to simulate a slow connection for testing on localhost.
You should remove this on production :)

Dekel
  • 60,707
  • 10
  • 101
  • 129
  • 3
    Perfect example for _force download using ajax_ – Vinay Dec 02 '16 at 02:38
  • 2
    @Dekel this helped me a lot while tracking download speed per MB – Ahmed Ali Nov 29 '18 at 11:50
  • @AhmedAli glad this helped :) – Dekel Nov 29 '18 at 16:01
  • 1
    healthy and helpful example. Thanks a lot. – Abhi Burk Dec 05 '18 at 12:16
  • 1
    If the file is big enough it's not good to put it in RAM. If it's small then a progressbar is not very usefull. This does not seem like a good idea. +1 for research and example. – Gherman Feb 12 '20 at 11:20
  • Thanks for the example, but this won't work if downloading from a different domain, will it? – bytrangle Sep 10 '21 at 15:40
  • @bytrangle not sure what do you mean by "downloading from a different domain" – Dekel Sep 10 '21 at 17:48
  • @Dekel: Sorry, I didn't notice that the method you used was POST, not GET, so I was talking about a different issue. Anyway, since you promptly reply, I'm risking asking a stupid question: Suppose you replace POST method with GET, and use a different domain as downloading url (e.g. domain-a.com) from the one your apps are hosted on (domain-b.com). If you don't control the server for `domain-a.com`, then you'll get an error "XMLHttpRequest blocked by CORS Policy", right? – bytrangle Sep 11 '21 at 14:55
  • @bytrangle, yes you are right (but this is less relevant for the current issue, so I suggest open a new question on that ;-) ). – Dekel Sep 12 '21 at 07:41