APEX spinner download indicator using Vanilla JavaScript (async/await)

There are plenty of good resources explaining how to create custom download links in APEX so I will skip over that part and list a couple here:

- https://oracle-base.com/articles/misc/apex-tips-file-download-from-a-button-or-link

- https://joelkallman.blogspot.com/2014/03/yet-another-post-how-to-link-to.html

These examples go into good detail on how to generate a custom link to download a file. But what about how to indicate that a file is downloading if it is taking a long time in the backend?

Problem

Some custom download processes can take quite sometime depending on what the process is doing in the backend (generation/business logic). It can be quite confusing to the end user that a process is occurring and for them to even know that they are waiting on downloading a file at all if this is the case.

If it takes a long time to run the process that ultimately downloads the file - then just setting a spinner on page submit or via apex.server.process will not necessarily achieve the desired outcome. Usually the apex engine is killed when you dedicate a page/process to download the file, and subsequently you can not easily signal that the download process is done and to take action upon that to kill a spinner if you had one going.

There are a few plugins out there that you can use in combination with each other as well. Lets just assume that you have a link already set up to download your file and that it kicks off some custom process to do so. Here is a quick Vanilla JavaScript approach to achieve a spinner while the download process is occurring and to remove it when it is done.

Solution

  1. Declare the following download function:
Page Properties -> Function and Global Variable Declaration
const download = async (url, filename) => {
    // add spinner
    var lSpinner$ = apex.util.showSpinner()
    const data = await fetch(url)
    const blob = await data.blob()
    const objectUrl = URL.createObjectURL(blob)
    const link = document.createElement('a')

    // set link download url/filename
    link.setAttribute('href', objectUrl)
    link.setAttribute('download', filename)
    link.style.display = 'none'

    // add link and download file
    document.body.appendChild(link)
    link.click();
    URL.revokeObjectURL(blob);
    link.remove();
    
    // remove spinner
    lSpinner$.remove();
};

This is utilizing async and await to make us wait for the promise to return a result. Majority of the waiting done is on the fetch here in my case as my process running takes over 5 seconds before it even sends a file back to the client. This allows us to confidently set a spinner before we wait for the result, and then remove the spinner after all of our code is done running.

2. Declare a Dynamic Action on button click to download the file

Dynamic Action -> Code

My download link runs a process that takes over 5 seconds to generate my file. I am using page items to hold the url and filename, so I used this:

download(apex.items.P1_DOWNLOAD_URL.value, apex.items.P1_FILENAME.value)

That is it. This concept can be used also to fire other events after a custom file generation process is done as well and not just to get a spinner. Hope this helps any of you that have long running download processes so that the end user is aware that something is occurring. Cheers!