Salketer Salketer - 6 months ago 11
PHP Question

How to display a loading animation while file is generated for download?

I have a web application where the user can generate PDF and PowerPoint files. These files may take some time to generate, so I would like to be able to display a loading animation while it generates. The problem here is that I have no mean to know when the download has started. The animation never goes away.

I am aware that it could be possible to generate the file "on the side" and alert the user when the file is ready for download using AJAX, but I prefer "locking" the user while he waits for the download to start.

Answer

To understand what needs to be done here, let's see what normally happens on this kind of request.

  1. User clicks the button to request the file.

  2. The file takes time to generate (the user gets no feedback).

  3. The file is finished and starts to be sent to user.

What we would like to add is a feedback for the user to know what we are doing... Between step 1 and 2 we need to react to the click, and we need to find a way to detect when step 3 occurred to remove the visual feedback. We will not keep the user informed of the download status, their browser will do it as with any other download, we just want to tell the user that we are working on their request.

For the file-generation script to communicate with our requester page's script we will be using cookies, this will assure that we are not browser dependent on events, iframes or the like. After testing multiple solutions this seemed to be the most stable from IE7 to latest mobiles.

Step 1.5: Display graphical feedback.

We will use javascript to display a notification on-screen. I've opted for a simple transparent black overlay on the whole page to prevent the user to interact with other elements of the page as following a link might make him loose the possibility to receive the file.

$('#downloadLink').click(function() {
  $('#fader').css('display', 'block');
});
#fader {
  opacity: 0.5;
  background: black;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<body>
  <div id="fader"></div>

  <a href="#path-to-file-generator" id="downloadLink">Click me to receive file!</a>
</body>

Step 3.5: Removing the graphical display.

The easy part is done, now we need to notify javascript that the file is being downloaded. When a file is sent to the browser, it is sent with the usual HTTP headers, this allows us to update the client cookies. We will leverage this feature to provide the proper visual feedback. Let's modify the code above, we will need to set the cookie's starting value, and listen to it's modifications.

var setCookie = function(name, value, expiracy) {
  var exdate = new Date();
  exdate.setTime(exdate.getTime() + expiracy * 1000);
  var c_value = escape(value) + ((expiracy == null) ? "" : "; expires=" + exdate.toUTCString());
  document.cookie = name + "=" + c_value + '; path=/';
};

var getCookie = function(name) {
  var i, x, y, ARRcookies = document.cookie.split(";");
  for (i = 0; i < ARRcookies.length; i++) {
    x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
    y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
    x = x.replace(/^\s+|\s+$/g, "");
    if (x == name) {
      return y ? decodeURI(unescape(y.replace(/\+/g, ' '))) : y; //;//unescape(decodeURI(y));
    }
  }
};

$('#downloadLink').click(function() {
  $('#fader').css('display', 'block');
  setCookie('downloadStarted', 0, 100); //Expiration could be anything... As long as we reset the value
  setTimeout(checkDownloadCookie, 1000); //Initiate the loop to check the cookie.
});
var downloadTimeout;
var checkDownloadCookie = function() {
  if (getCookie("downloadStarted") == 1) {
    setCookie("downloadStarted", "false", 100); //Expiration could be anything... As long as we reset the value
    $('#fader').css('display', 'none');
  } else {
    downloadTimeout = setTimeout(checkDownloadCookie, 1000); //Re-run this function in 1 second.
  }
};
#fader {
  opacity: 0.5;
  background: black;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<body>
  <div id="fader"></div>

  <a href="#path-to-file-generator" id="downloadLink">Click me to receive file!</a>
</body>

Ok, what have we added here. I've put the set/getCookie functions I use, I don't know if they are the best, but they work very well. We set the cookie value to 0 when we initiate the download, this will make sure that any other past executions will not interfere. We also initiate a "timeout loop" to check the value of the cookie every second. This is the most arguable part of the code, using a timeout to loop function calls waiting for the cookie change to happen may not be the best, but it has been the easiest way to implement this on all browsers. So, every second we check the cookie value and, if the value is set to 1 we hide the faded visual effect.

Changing the cookie server side

In PHP, one would do like so:

setCookie("downloadStarted", 1, time() + 20, '/', "", false, false);

Name of the cookie is downloadStarted, its value is 1, it expires in NOW + 20seconde (we check every second so 20 is more than enough for that, change this value if you change the timeout value in the javascript), its path is on the whome domain (change this to your liking), its not secured as it contains no sensitive data and it is NOT HTTP only so our javascript can see it.

VoilĂ ! That sums it up. Please note that the code provided works perfectly on a production application I am working with but might not suit your exact needs, correct it to your taste.