Lazy Download An alternative to DOM.download that lazily computes the value to be downloaded, and allows the value to be specified asynchronously. However, the downside is that two clicks are required if the value is asynchronous: first to save the value, and then the second to download it.
syncExample = download(() => { return new Blob( [JSON.stringify({hello: "world"})], {type: "application/json"} ); }, "helloworld.json", "Save a synchronous value with one click")
asyncExample = download(async () => { await Promises.delay(1000); return new Blob( [JSON.stringify({hello: "world"})], {type: "application/json"} ); }, "helloworld.json", "Save an asynchronous value with two clicks")
When used with asynchronous values, dispatching a reset event to the generated HTML element cancels the saved value:
b = { const d = html`<button>Reset asynchronous download button`; d.onclick = event => asyncExample.dispatchEvent(new CustomEvent("reset")); return d; }
function download(value, name = "untitled", label = "Save") { const a = html`<a><button></button></a>`; const b = a.firstChild; b.textContent = label; a.download = name; async function reset() { await new Promise(requestAnimationFrame); URL.revokeObjectURL(a.href); a.removeAttribute("href"); b.textContent = label; b.disabled = false; } a.onclick = async event => { b.disabled = true; if (a.href) return reset(); // Already saved. b.textContent = "Saving…"; try { const object = await value(); b.textContent = "Download"; a.href = URL.createObjectURL(object); } catch (ignore) { b.textContent = label; } if (event.eventPhase) return reset(); // Already downloaded. b.disabled = false; }; a.addEventListener("reset", reset); return a; }