1

I've implemented a file download button through ajax using code similar to this answer. I have code that looks something like this:

$('#download-link').on('click', function (event) {
    event.preventDefault();
    var link = $('#download-link').attr('href');
    $.ajax({
        url: link,
        success: function (result) {

            var blob = getBlob(result);
            var a = document.createElement('a');
            var url = (window.URL ? URL : webkitURL).createObjectURL(blob);
            a.href = url;
            a.download = 'somefilename.txt';
            a.style.display = 'none';
            document.body.appendChild(a);                       
            try {
                a.click();
            }
            catch (err) {
                //fallback. if .click() is unavailable display a new button for user to get file.
                a.style.display = '';
                a.innerText = 'Get File';
                var downloadButton = document.getElementById('download-link');
                downloadButton.style.display = 'none';
            }
        }
    });
});

So essentially, we're creating an object URL, creating an <a> element with Href of the object URL and calling Click() on that element to download the file. Some browsers will throw an error at that point, so as a fallback we catch the error and display the created button, so that the user can press and get the file.

The problem is that some browsers (seems to be on iOS, and older versions of Android) will simply ignore the Click() call. I know that I could use browser sniffing to deal with this, but I don't consider that a very good solution. Is there anything more elegant that I can do to detect this situation and use my fallback?

Slappywag
  • 1,143
  • 1
  • 17
  • 27
  • 2
    Whats wrong with a plain old anchor?? – Jonas Wilms Sep 01 '17 at 13:15
  • I believe the common UX pattern is to have your link text to something like "If your download does not start click here" instead of hiding it –  Sep 01 '17 at 13:16
  • Do you need ajax for this? how about just doing: `$('#download-link').attr('download', 'somefilename.txt')` probably don't need any js at all – Endless Sep 01 '17 at 13:27
  • The example is simplified. In reality the server may well not be able to create the file, so I want to gracefully show the reason the file can't be return in my page. – Slappywag Sep 01 '17 at 13:37

2 Answers2

2

You could create a flag to check if your script was able to trigget the click event. Listen for a click and flip the flag, and after some time handle the case where no click was detected.

        a.href = url;
        a.download = 'somefilename.txt';
        a.style.display = 'none';
        var linkClicked = false;
        a.addEventListener('click',()=>linkClicked = true);
        document.body.appendChild(a);                       
        a.click();
        setTimeout(()=>{
            if(!linkClicked){
            //fallback. if .click() is unavailable display a new button for user to get file.
            a.style.display = '';
            a.innerText = 'Get File';
            var downloadButton = document.getElementById('download-link');
            downloadButton.style.display = 'none';
            }
        },500);

Another solution is to always show the link but have the link text something along the lines "If your download does not start click here"

  • This is definitely the more graceful solution I was looking for, although I think always showing the link would probably be the best idea. Thanks – Slappywag Sep 01 '17 at 13:39
0

@Professor Allman has already provided an elegant solution by detecting .click() feature, instead of sniffing browser.

To make the code easier to use, I'd like to refactor the code, add cache, and wrap it as a single function:

function hasClickFeature(callback) {
  if (window.hasAnchorClickFeature !== undefined) {
    callback(window.hasAnchorClickFeature);
  }

  var a = document.createElement('a');
  var hasClick = false;
  a.href='javascript:;';
  a.style.display = 'none';
  a.addEventListener('click', function() {
    hasClick = true;
    window.hasAnchorClickFeature = hasClick;
    callback(hasClick);
  });
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    if (!hasClick) {
      window.hasAnchorClickFeature = hasClick;
      callback(hasClick);
    }
  }, 500);
}

hasClickFeature(function(hasClick) {
  console.log(hasClick);
});
shaochuancs
  • 15,342
  • 3
  • 54
  • 62