Sunday, 26 April 2020

How to Download Data From Parent & Child Objects as a CSV File in Lightning using Aura Components

Use Case: Todays post is about how to download/export data from your lightning(Aura) component as a CSV file. This is the most common expectation these days by business users where they want to download the data present in tabular format so that they can use it for further analysis. There are couple of other blogs where you can find the code for this scenario but their logic do not support when you pull data from child and parent objects in single query. So I have spent some time refactoring some of the code available and based on user comments to address those missing parts. 

Below is the Lightning Component which displays list of opportunities along with download button

<aura:component controller="CSVExportController">
<aura:handler name="init" value="{!this}" action="{!c.loadOpps}"/>
<aura:attribute name="oppList" type="opportunity[]" description="list variable to hold the opportunities returned"/>
<!-- sample table component structure from https://www.lightningdesignsystem.com/components/data-tables/-->
<div class="slds-m-around--xx-large">
<button class="slds-button slds-button--brand" onclick="{!c.downloadCSVFile}">Download As CSV</button> <br/><br/>
<table class="slds-table slds-table_cell-buffer slds-table_bordered">
<thead>
<tr class="slds-line-height_reset">
<th class="" scope="col">
<div class="slds-truncate" title="Opportunity Name">Opportunity Name</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Account Name">Account Name</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Close Date">Close Date</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Stage">Stage</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Amount">Amount</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Industry">Industry</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Owner">Owner</div>
</th>
</tr>
</thead>
<tbody>
<aura:iteration items="{!v.oppList}" var="opp"> <!-- iterate over rows returned -->
<tr class="slds-hint-parent">
<th data-label="Opportunity Name" scope="row">
<div class="slds-truncate">
<a href="javascript:void(0);" tabindex="-1">{!opp.Name}</a>
</div>
</th>
<td data-label="Account Name">
<div class="slds-truncate">{!opp.Account.Name}</div>
</td>
<td data-label="Close Date">
<div class="slds-truncate">{!opp.CloseDate}</div>
</td>
<td data-label="Stage">
<div class="slds-truncate">{!opp.StageName}</div>
</td>
<td data-label="Amount">
<div class="slds-truncate">${!opp.Amount}</div>
</td>
<td data-label="Industry">
<div class="slds-truncate">{!opp.Account.Industry}</div>
</td>
<td data-label="Owner">
<div class="slds-truncate">{!opp.Owner.Name}</div>
</td>
</tr>
</aura:iteration>
</tbody>
</table>
</div>
</aura:component>

Javascript controller

({
// on load function call
loadOpps: function(component, event, helper){
helper.onLoad(component, event);
},
// function call on click of "Download As CSV" button
downloadCSVFile : function(component,event,helper){
// call the helper function which prepares the data for csv file
var csv = helper.prepareCSVFile(component,component.get("v.oppList"));
if (csv == null){
return; // if no data found return null
}
//generic code taken from internet which helps you in downloading the csvfile
var hiddenElement = document.createElement('a');
hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv); // defines the href parameters
hiddenElement.target = '_self';
hiddenElement.download = 'oppsdata.csv'; // Name of the csv file
document.body.appendChild(hiddenElement); // Required for FireFox browser
hiddenElement.click();
}
})

Javascript helper which holds the logic of formatting the data to make it compatible to download parent and child records without any issues

({
onLoad: function(component, event) {
var action = component.get('c.getOpportunities'); // call to your controller with no parameters
action.setCallback(this, function(response){
var state = response.getState();
if (state === "SUCCESS") {
component.set('v.oppList', response.getReturnValue()); // set the oppList variable with data retruned from your controller
}else{
// Error handling logic goes here
}
});
$A.enqueueAction(action);
},
prepareCSVFile : function(component,oppsData){
// here we add some additional logic to prepare the data so that you can download both parent and child records
var obj = {}; //empty object
var opps = new Array(); // array to store all opps after processing
//loop through all the records and prepare a new object with your own variables
//If it has name like Account.Name where you see undefined in the csv sheet
//change it to AccountName in your new data
for(var i=0; i < oppsData.length; i++){
obj = {
"Id": oppsData[i].Id,
"Name": oppsData[i].Name,
"AccountName": oppsData[i].Account.Name,
"CloseDate": oppsData[i].CloseDate,
"StageName": oppsData[i].StageName,
"Amount": oppsData[i].Amount,
"Industry": oppsData[i].Account.Industry,
"OwnerName": oppsData[i].Owner.Name
};
opps.push(obj);
}
// end of additional logic
// csv sheet data logic
var csvStringResult, counter, keys, columnDivider, lineDivider;
// check if data is null
if (opps == null || !opps.length) {
return null;
}
columnDivider = ',';
lineDivider = '\n';
// Labels for csv file header row
keys = ['Id','Name','AccountName','CloseDate','StageName','Amount','Industry','OwnerName' ];
csvStringResult = '';
csvStringResult += keys.join(columnDivider);
csvStringResult += lineDivider;
for(var i=0; i < opps.length; i++){
counter = 0;
for(var sTempkey in keys) {
var skey = keys[sTempkey] ;
if(counter > 0){
csvStringResult += columnDivider;
}
if(opps[i][skey] != undefined){
csvStringResult += '"'+ opps[i][skey]+'"';
}else{
csvStringResult += '"'+ '' +'"';
}
counter++;
}
csvStringResult += lineDivider;
}
return csvStringResult;
}
})


And a couple of screenshots to show how it looks:



Hit the Download As CSV button will download the data and when it is opened you can see data in below format:


If this post really helped you to fix your issue please let me know in the comments section. Also please share it with your friends who can benefit from similar situation.