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
- Declare the following download function:
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
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!