4

I am trying to create a .html file using Apex and when a user clicks a button on the front-end, they can download the .html file. Below is the code I have and when I am loading the component and clicking on the "Download Report" button, the button is just displaying the text [object Object] instead of downloading the attachment "attach". How can I make the report download to a users' computer? Appreciate your help on this!

Here's the lightning component:

<aura:component implements="force:appHostable, 
                        flexipage:availableForAllPageTypes,
                        flexipage:availableForRecordHome,
                        force:hasRecordId"
            access="global"
            controller="DownloadController">

    <aura:attribute name="downloadURL" type="String"/> 

    <lightning:button label="Download Report" 
                    iconName="utility:download"
                    iconPosition="right"
                    onclick="{!c.handleClick}"/>

    <ul>
        {!v.downloadURL}
    </ul>

</aura:component>   

Here's the JS Controller:

({
    handleClick : function(component, event, helper) {
    var action = component.get("c.ReportDownloadController"); //getting attachment from apex

    action.setCallback(this, function(response) {
           let state = response.getState();
           if (state === "SUCCESS") {
                component.set("v.downloadURL", response.getReturnValue());
               // console.log(response.getReturnValue());
           } else {
                console.log("Failed with state: " + state);
           }
    })

    $A.enqueueAction(action);
    }
})

And here's the Apex Controller:

public with sharing class DownloadController {

    @AuraEnabled
    public static Attachment DownloadController() {
        String txt = '<b>hello</b>';
        Blob txtblob = Blob.valueOf(txt); //converting string to a blob
        Attachment attach = new Attachment();
        attach.Name = 'text.html';
        attach.Body = txtblob;
        attach.ContentType = 'text/html';
        return attach;    
    }
}

2 Answers 2

3

The Attachment object is for saving data to the database. It has a number of fields, which is why you're seeing [object Object]. Instead, you should be able to do so with code like this:

@AuraEnabled public static String getFile() {
  String htmlText = '<html>...</html>';
  return EncodingUtil.base64Encode(Blob.valueOf(htmlText));
}

action.setCallback(this, function(response) {
       let state = response.getState();
       if (state === "SUCCESS") {
           let downloadLink = document.createElement("a");
           downloadLink.href = "data:text/html;base64,"+response.getReturnValue();
           downloadLink.download = "filename.html";
           downloadLink.click();
       } ...
3
  • 1
    thanks for the help! This is precisely what I was trying to do. Just a small typo in the code above. Instead of download.click(), need to use downloadLink.click().
    – Illusion
    Jan 13, 2019 at 3:13
  • @Illusion Yeah, thanks for that. Fixed the typo. Glad I was able to help!
    – sfdcfox
    Jan 13, 2019 at 3:15
  • This solution doesn't work in Firefox. I have proposed a new answer -- see below. May 23, 2019 at 20:43
0

sfdcfox's answer got me most of the way there, but I discovered that his approach to calling the click() method on an element won't work in Firefox.

Reference: https://stackoverflow.com/questions/32225904/programmatical-click-on-a-tag-not-working-in-firefox

To address the Firefox limitation, you have to explicitly add the created element to the DOM.

Apex Controller:

@AuraEnabled public static String getFile() {
  String htmlText = '<html>...</html>';
  return EncodingUtil.base64Encode(Blob.valueOf(htmlText));
}

Javascript Controller/Helper:

action.setCallback(this, function(response) {
    let state = response.getState();
    if (state === "SUCCESS") {
        let downloadLink = document.createElement("a");
        downloadLink.setAttribute("type", "hidden");
        downloadLink.href = "data:text/html;base64,"+response.getReturnValue();
        downloadLink.download = "filename.html";
        document.body.appendChild(downloadLink);
        downloadLink.click();
        downloadLink.remove();
    } ...

This works in both Chrome and Firefox, until your file gets to be ~2 MB. It doesn't address an apparent Chrome limitation that prevents the download of files greater than 2 MB, based on URL size limitations and the fact that we are utilizing data URLs.

Reference: https://stackoverflow.com/questions/695151/data-protocol-url-size-limitations

Here is a more complex approach that addresses the Chrome limitation. It seems to work, although I haven't wrapped my head around all the elements.

Reference: https://stackoverflow.com/questions/10473932/browser-html-force-download-of-image-from-src-dataimage-jpegbase64/10473992#10473992

action.setCallback(this, function(response) {
    let state = response.getState();
    if (state === "SUCCESS") {
        var base64Url = "data:text/html;base64,"+response.getReturnValue();
        var decodedData = atob(response.getReturnValue());
        var arrayBuffer = new ArrayBuffer(decodedData.length);
        var view = new UintArray(arrayBuffer);
        for (var i = 0; i < decodedData.length; i++) {
            view[i] = decodedData.charCodeAt(i) & 0xff;
        }
        var blob = new Blob([arrayBuffer], {type: 'application/octet-stream'});
        var blobUrl = URL.createObjectURL(blob);
        let downloadLink = document.createElement("a");
        downloadLink.setAttribute("type", "hidden");
        downloadLink.href = blobUrl;
        downloadLink.download = "filename.html";
        document.body.appendChild(downloadLink);
        downloadLink.click();
        downloadLink.remove();
    } ...

While this one works, it seems like a lot of code to me. There are quite a few dependencies that could potentially break under various scenarios. I'd be happy if someone could provide a shorter version that achieves the same ends.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .