Pages

Showing posts with label CSV. Show all posts
Showing posts with label CSV. Show all posts

Friday, July 31, 2026

How to Convert CSV Response in to Excel sheet with Download by using LWC

How to Convert CSV Response into an Excel Sheet with Download by Using LWC

Reporting screens often need two things at once: a readable table on screen, and a downloadable file the user can open in Excel. Rather than building two separate data pipelines, you can fetch a single CSV string from Apex, render it as a table in your Lightning Web Component, and let the same string be downloaded as a .csv file with one click.

This post walks through a working pattern for exactly that — fetching report data, parsing raw CSV text into a JavaScript object array, displaying it in the UI, and exporting it back out as a downloadable spreadsheet.

Step 1: Fetch the Report Data from Apex

The component calls an imperative Apex method, passing along whatever filters the user has selected (in this case, a searchWrapper object holding search parameters):

getReportData() {
    this.isLoading = true;

    getReportData({
        searchParams: this.searchWrapper
    })
    .then(result => {
        this.csvdata = result;
        this.formatReportData(result);
        this.isLoading = false;
    })
    .catch(error => {
        console.error('getReportData error', error);
        this.isLoading = false;
    });
}

The Apex method is expected to return a raw CSV string (header row + data rows). That string is stored as-is in this.csvdata — this is important, because it's what gets handed off later for the file download, untouched by any formatting done for display purposes.

Step 2: Turn the CSV String into an Array of Objects

To show the data in a table (or any other UI component), the raw CSV text needs to become structured JavaScript objects. The formatReportData method handles this:

formatReportData(info) {
    var resultInfo = info.trim().split(/\r?\n|\r/);
    var headerColumns = resultInfo[0].replaceAll(' ', '_').trim().split(',');
    var dataObj = [];

    for (let index = 0; index < resultInfo.length; ++index) {
        const element = resultInfo[index];
        if (index !== 0) {
            var rowData = this.CSVtoArray(element);
            var obj = {};
            for (let colIndex = 0; colIndex < rowData.length; ++colIndex) {
                obj[headerColumns[colIndex]] = rowData[colIndex];
            }
            dataObj.push(obj);
        }
    }

    this.reportData = dataObj;
    this.showDataResult = dataObj.length > 0;
}

A few things worth noting here:

  • Line splitting is resilient. split(/\r?\n|\r/) handles CSV files coming from Windows (\r\n), Mac (\r), or Unix (\n) line endings, which matters since exported reports can originate from different systems.
  • Header names are sanitized. Spaces in column headers are replaced with underscores, making them safe to use as JavaScript object keys.
  • The first row is skipped when building data rows, since it only contains column headers.
  • showDataResult acts as a simple flag to conditionally show or hide the results table in the template, depending on whether any rows came back.

Step 3: Parse Each Row Safely with a Regex-Based CSV Splitter

A simple .split(',') breaks as soon as a field contains a comma inside quotes (for example, "Doe, John"). To avoid that, the component uses a small, well-tested regex-based parser:

CSVtoArray(text) {
    var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
    var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;

    if (!re_valid.test(text)) return null;

    var a = [];
    text.replace(re_value, function(m0, m1, m2, m3) {
        if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));
        else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
        else if (m3 !== undefined) a.push(m3);
        return '';
    });

    if (/,\s*$/.test(text)) a.push('');
    return a;
}

This function:

  1. First validates the line against re_valid to confirm it's a well-formed CSV row — returning null immediately if not.
  2. Then walks through the line with re_value, correctly extracting values that are single-quoted, double-quoted, or unquoted.
  3. Unescapes any backslash-escaped quotes inside quoted values.
  4. Handles the edge case of a trailing empty value (a line ending in a comma).

Because this parser respects quotes, it can safely handle addresses, names, or notes fields that contain embedded commas — something a naive split would corrupt.

Step 4: Let the User Download the Same Data as a CSV File

Since the raw CSV string was preserved in this.csvdata, generating a downloadable file just means creating a temporary link element and triggering a click on it:

getDatasheet() {
    let csv = this.csvdata;
    if (csv == null) { return; }

    var hiddenElement = document.createElement('a');
    hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
    hiddenElement.target = '_self';
    hiddenElement.download = 'Export.csv';
    document.body.appendChild(hiddenElement);
    hiddenElement.click();
}

Because the file is saved with a .csv extension and a text/csv MIME type, double-clicking it on most machines opens it directly in Excel or Google Sheets — no server round trip or extra Apex logic required for the export itself.

Step 5: Wiring Up Filter Selections

The component also supports multi-select filters for material and plant, which get converted into comma-separated strings before being sent to Apex:

handleMaterial(event) {
    let selectedMaterial = event.detail
        .map(x => x.selected === true ? x.value : null)
        .filter(v => v !== null);
    this.searchWrapper.material = selectedMaterial.toString();
}

handlePlant(event) {
    let selectedPlant = event.detail
        .map(x => x.selected === true ? x.value : null)
        .filter(v => v !== null);
    this.searchWrapper.plant = selectedPlant.toString();
}

These selections feed directly into searchWrapper, which is passed to getReportData() on the next fetch.

Key Takeaways

  • Keep the raw CSV string intact for downloading, and only transform a copy of it for on-screen display — this avoids double work and keeps the exported file identical to the source data.
  • Use a regex-based CSV parser instead of a plain split(',') whenever fields might contain commas or quoted values.
  • Normalize line endings with split(/\r?\n|\r/) so the parser works regardless of which OS generated the file.
  • Generating a downloadable file client-side with a data:text/csv URI avoids extra server calls — the browser handles the file creation.
  • Toggle a boolean flag (like showDataResult) based on whether parsed data exists, so your template can cleanly show empty states versus populated tables.

With this approach, a single Apex call and a lightweight JavaScript parser give you both an interactive report table and a one-click Excel-ready export — all inside one LWC.