<template> <a v-if="!downloadXHR" :href="`${url}?dl=${filename}`" target="_blank" :download="filename" > <slot></slot> </a> <a v-else :href="`${url}`" target="_blank" :download="filename" :class="{ [loadingClass]: loading }" @click="onClick($event)" > <slot></slot> </a> </template> <script> export default { name: 'DownloadLink', props: { url: { type: String, required: true, }, filename: { type: String, required: true, }, onError: { type: Function, required: false, default: null, }, loadingClass: { type: String, required: true, }, }, data() { return { loading: false } }, computed: { downloadXHR() { return this.$env.DOWNLOAD_FILE_VIA_XHR === '1' }, }, methods: { async download() { this.loading = true // We are using fetch here to avoid extra header // as we need to add them to CORS later const response = await fetch(this.url) const blob = await response.blob() const data = window.URL.createObjectURL(blob) this.loading = false // Create temporary anchor element to trigger the download const a = document.createElement('a') a.style = 'display: none' a.href = data a.target = '_blank' a.download = this.filename document.body.appendChild(a) a.onclick = (e) => { // prevent modal/whatever closing e.stopPropagation() } a.click() setTimeout(function () { // Remove the element document.body.removeChild(a) // Release resource on after triggering the download window.URL.revokeObjectURL(data) }, 500) }, onClick(event) { if (this.downloadXHR) { event.preventDefault() this.download().catch((error) => { // In any case, to be sure the loading animation will not last this.loading = false if (this.onError) { this.onError(error) } else { throw error } }) } }, }, } </script>