function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Rebekah LillyRebekah Lilly 

Export LWC Table to Excel using XLSX format.

I have a fairly simply program that I am using to retreive some records via Apex and then allow the user to filter them in the JS controller. I also have an Export List button that is currently allowing them to download the list they have filtered as a CSV file. The problem is that leading zeroes do not show up in Excel. This is not an LWC or Salesforce issue, but an issue with how Excel handles the data. I would like to create a real XLSX file. Is there a way to do this natively? Or will I need to use a third-party library? What would be the recommended library to use? xlsx.js?

Thanks,
Rebekah

handleExport() {
        let rowEnd = '\n';
        let csvString = '';
        this.exportHeader.forEach(element => {
            csvString += element + ',';
        });
        csvString += rowEnd;
        this.orders.forEach(record => {
            csvString += Customer__c + ',';
            csvString += '"' + record.ShipToLocationID__c + '",';
            csvString += '"' + record.PONumber__c + '",';
            if (record.POLineNumber__c == undefined) {
                csvString += ' ,';
            } else {
                csvString += record.POLineNumber__c + ',';
            }
            if (record.CustomerItemNumber__c == undefined) {
                csvString += ' ,';
            } else {
               csvString += '"' + record.CustomerItemNumber__c + '",';
            }
            csvString += '"' + record.ItemNumber__c + '",';
            csvString += '"' + record.CONumber__c + '",';
            csvString += '"' + record.ERP_ProductItem__r.ItemDescription__c + '",';
            csvString += record.PromisedDeliveryDate__c + ',';
            csvString += record.COLineNumber__c + ',';
            csvString += record.ItemOrderedQuantity__c + ',';
            csvString += record.OpenQuantity__c + ',';
            csvString += record.UnitPrice__c + ',';
            csvString += record.ExtendedPrice__c + ',';
            csvString += rowEnd;
        });
       
        var element = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvString);
        let downloadElement = document.createElement('a');
        downloadElement.href = element;
        downloadElement.target = '_self';
        downloadElement.download = 'Sales Order Lines.csv';
        document.body.appendChild(downloadElement);
        downloadElement.click();
    }
Best Answer chosen by Rebekah Lilly
Alain CabonAlain Cabon
Ok, I verified https://dev.to/karkranikhil/multiple-worksheets-in-excel-using-lightning-web-components-4fpp   and there are little things to change

Promise.all([loadScript(this, workbook + "/xlsx.full.min.js")])    ( if you create a direct zip like me of xlsx.full.min.js without folder )

Two missing classes indeed:  
getAccountList (without "s" )
public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccountList() {
        return [SELECT Id, Name FROM Account WITH SECURITY_ENFORCED LIMIT 10];
    }
}
 
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [SELECT Id, Name, Title, Phone, Email FROM Contact WITH SECURITY_ENFORCED LIMIT 10];
    }
}
getContactList without "s"
import { LightningElement, track } from "lwc";
import getContactLists from "@salesforce/apex/ContactController.getContactList";
import getAccountLists from "@salesforce/apex/AccountController.getAccountList";
export default class XlsxDemo extends LightningElement {
  @track xlsHeader = []; // store all the headers of the the tables
  @track workSheetNameList = []; // store all the sheets name of the the tables
  @track xlsData = []; // store all tables data
  @track filename = "nikhil_demo.xlsx"; // Name of the file
  @track accountData = []; // used only for storing account table
  @track contactData = []; // used only for storing contact table

  connectedCallback() {
    //apex call for bringing the contact data  
    getContactLists()
      .then(result => {
        console.log(result);
        this.contactHeader = Object.keys(result[0]);
        this.contactData = [...this.contactData, ...result];
        this.xlsFormatter(result, "Contacts");
      })
      .catch(error => {
        console.error(error);
      });
    //apex call for bringing the account data  
    getAccountLists()
      .then(result => {
        console.log(result);
        this.accountHeader = Object.keys(result[0]);
        this.accountData = [...this.accountData, ...result];
        this.xlsFormatter(result, "Accounts");
      })
      .catch(error => {
        console.error(error);
      });
  }

  // formating the data to send as input to  xlsxMain component
  xlsFormatter(data, sheetName) {
    let Header = Object.keys(data[0]);
    this.xlsHeader.push(Header);
    this.workSheetNameList.push(sheetName);
    this.xlsData.push(data);
  }

   // calling the download function from xlsxMain.js 
  download() {
    this.template.querySelector("c-xlsx-main").download();
  }
}

Promise.all([loadScript(this, workbook + "/xlsx.full.min.js")])
//xlsxMain.js
import { LightningElement, api } from "lwc";
import { loadScript } from "lightning/platformResourceLoader";
import workbook from "@salesforce/resourceUrl/xlsx";
export default class XlsxMain extends LightningElement {
  @api headerList;
  @api filename;
  @api worksheetNameList;
  @api sheetData;
  librariesLoaded = false;
  renderedCallback() {
    console.log("renderedCallback xlsx");
    if (this.librariesLoaded) return;
    this.librariesLoaded = true;
    Promise.all([loadScript(this, workbook + "/xlsx.full.min.js")])
      .then(() => {
        console.log("success");
      })
      .catch(error => {
        console.log("failure");
      });
  }
  @api download() {
    const XLSX = window.XLSX;
    let xlsData = this.sheetData;
    let xlsHeader = this.headerList;
    let ws_name = this.worksheetNameList;
    let createXLSLFormatObj = Array(xlsData.length).fill([]);
    //let xlsRowsKeys = [];
    /* form header list */
      xlsHeader.forEach((item, index) => createXLSLFormatObj[index] = [item])

    /* form data key list */
      xlsData.forEach((item, selectedRowIndex)=> {
          let xlsRowKey = Object.keys(item[0]);
          item.forEach((value, index) => {
              var innerRowData = [];
              xlsRowKey.forEach(item=>{
                  innerRowData.push(value[item]);
              })
              createXLSLFormatObj[selectedRowIndex].push(innerRowData);
          })

      });
    /* creating new Excel */
    var wb = XLSX.utils.book_new();

    /* creating new worksheet */
    var ws = Array(createXLSLFormatObj.length).fill([]);
    for (let i = 0; i < ws.length; i++) {
      /* converting data to excel format and puhing to worksheet */
      let data = XLSX.utils.aoa_to_sheet(createXLSLFormatObj[i]);
      ws[i] = [...ws[i], data];

      /* Add worksheet to Excel */
      XLSX.utils.book_append_sheet(wb, ws[i][0], ws_name[i]);
    }

    /* Write Excel and Download */
    XLSX.writeFile(wb, this.filename);
  }
}

User-added image


Download ok of an Excel file generated correctly.

All Answers

Alain CabonAlain Cabon

The most detailed article is this one with a precise link to xlsx.js usable directly:
https://dev.to/karkranikhil/multiple-worksheets-in-excel-using-lightning-web-components-4fpp

There are many problems otherwise like this one (with solution):  https://salesforce.stackexchange.com/questions/186448/using-xslx-js-library-on-lightning-component

This other technique is simpler (very close to yours):  https://www.apexhours.com/export-to-csv-xls-using-lightning-web-component/
 
Rebekah LillyRebekah Lilly
Has anyone used these methods?

I tried this : https://dev.to/karkranikhil/multiple-worksheets-in-excel-using-lightning-web-components-4fpp and I get an error on this line : var wb = XLSX.utils.book_new(); that utils does not exist.

I also tried : https://learn.habilelabs.io/salesforce-export-to-excel-with-lightning-web-component-875b3f4b7e53 and I get an error on this line : writeXlsxFile(...... not a function

I am able to successfully create a CSV file download with no problem. The request is for a .XLS.

I found a way to work with a VisualForce page, but it gives me an error when opening it that the file extension doesn't match the format, but still opens correctly.

I am just surprised that this is so difficult in Salesforce... I have worked with Excel successfully for years in COBOL, lol. 
Alain CabonAlain Cabon
Ok, I verified https://dev.to/karkranikhil/multiple-worksheets-in-excel-using-lightning-web-components-4fpp   and there are little things to change

Promise.all([loadScript(this, workbook + "/xlsx.full.min.js")])    ( if you create a direct zip like me of xlsx.full.min.js without folder )

Two missing classes indeed:  
getAccountList (without "s" )
public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccountList() {
        return [SELECT Id, Name FROM Account WITH SECURITY_ENFORCED LIMIT 10];
    }
}
 
public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactList() {
        return [SELECT Id, Name, Title, Phone, Email FROM Contact WITH SECURITY_ENFORCED LIMIT 10];
    }
}
getContactList without "s"
import { LightningElement, track } from "lwc";
import getContactLists from "@salesforce/apex/ContactController.getContactList";
import getAccountLists from "@salesforce/apex/AccountController.getAccountList";
export default class XlsxDemo extends LightningElement {
  @track xlsHeader = []; // store all the headers of the the tables
  @track workSheetNameList = []; // store all the sheets name of the the tables
  @track xlsData = []; // store all tables data
  @track filename = "nikhil_demo.xlsx"; // Name of the file
  @track accountData = []; // used only for storing account table
  @track contactData = []; // used only for storing contact table

  connectedCallback() {
    //apex call for bringing the contact data  
    getContactLists()
      .then(result => {
        console.log(result);
        this.contactHeader = Object.keys(result[0]);
        this.contactData = [...this.contactData, ...result];
        this.xlsFormatter(result, "Contacts");
      })
      .catch(error => {
        console.error(error);
      });
    //apex call for bringing the account data  
    getAccountLists()
      .then(result => {
        console.log(result);
        this.accountHeader = Object.keys(result[0]);
        this.accountData = [...this.accountData, ...result];
        this.xlsFormatter(result, "Accounts");
      })
      .catch(error => {
        console.error(error);
      });
  }

  // formating the data to send as input to  xlsxMain component
  xlsFormatter(data, sheetName) {
    let Header = Object.keys(data[0]);
    this.xlsHeader.push(Header);
    this.workSheetNameList.push(sheetName);
    this.xlsData.push(data);
  }

   // calling the download function from xlsxMain.js 
  download() {
    this.template.querySelector("c-xlsx-main").download();
  }
}

Promise.all([loadScript(this, workbook + "/xlsx.full.min.js")])
//xlsxMain.js
import { LightningElement, api } from "lwc";
import { loadScript } from "lightning/platformResourceLoader";
import workbook from "@salesforce/resourceUrl/xlsx";
export default class XlsxMain extends LightningElement {
  @api headerList;
  @api filename;
  @api worksheetNameList;
  @api sheetData;
  librariesLoaded = false;
  renderedCallback() {
    console.log("renderedCallback xlsx");
    if (this.librariesLoaded) return;
    this.librariesLoaded = true;
    Promise.all([loadScript(this, workbook + "/xlsx.full.min.js")])
      .then(() => {
        console.log("success");
      })
      .catch(error => {
        console.log("failure");
      });
  }
  @api download() {
    const XLSX = window.XLSX;
    let xlsData = this.sheetData;
    let xlsHeader = this.headerList;
    let ws_name = this.worksheetNameList;
    let createXLSLFormatObj = Array(xlsData.length).fill([]);
    //let xlsRowsKeys = [];
    /* form header list */
      xlsHeader.forEach((item, index) => createXLSLFormatObj[index] = [item])

    /* form data key list */
      xlsData.forEach((item, selectedRowIndex)=> {
          let xlsRowKey = Object.keys(item[0]);
          item.forEach((value, index) => {
              var innerRowData = [];
              xlsRowKey.forEach(item=>{
                  innerRowData.push(value[item]);
              })
              createXLSLFormatObj[selectedRowIndex].push(innerRowData);
          })

      });
    /* creating new Excel */
    var wb = XLSX.utils.book_new();

    /* creating new worksheet */
    var ws = Array(createXLSLFormatObj.length).fill([]);
    for (let i = 0; i < ws.length; i++) {
      /* converting data to excel format and puhing to worksheet */
      let data = XLSX.utils.aoa_to_sheet(createXLSLFormatObj[i]);
      ws[i] = [...ws[i], data];

      /* Add worksheet to Excel */
      XLSX.utils.book_append_sheet(wb, ws[i][0], ws_name[i]);
    }

    /* Write Excel and Download */
    XLSX.writeFile(wb, this.filename);
  }
}

User-added image


Download ok of an Excel file generated correctly.
This was selected as the best answer
Alain CabonAlain Cabon
nikhil demo xlsx :  two sheets

User-added image

User-added image

 
Rebekah LillyRebekah Lilly
Thank you for your help Alain! I was able to get this working and my business loves it!
Alain CabonAlain Cabon
Congrats on your successful code! It is an advanced level of javascrpt here. Still difficult to adapt for a different need even if the example is complete in the article.