Dynamic File Upload Component in Salesforce LWC
Build an LWC allowing users to dynamically configure the object and fields they want to upload data to, download a template for these fields, and more.
Join the DZone community and get the full member experience.
Join For FreeIn Salesforce, the ability to import and export data dynamically is crucial for many business processes. While standard Salesforce tools provide basic import/export functionality, custom solutions can offer enhanced flexibility and user experience. In this blog, we will build a Lightning Web Component (LWC) that allows users to dynamically configure the object and fields they want to upload data to, download a template for these fields, and then upload data. Furthermore, we’ll display error and success messages in a popup modal to improve user interaction.
By the end of this tutorial, you will have a reusable component that simplifies data imports and guides users through the process with clear feedback.
1. Setting Up the LWC Component
Our component will include:
- Object and field configuration from Meta XML: The object name and fields will be configured using properties defined in the LWC’s
js-meta.xml
file. - Downloadable template: The component will generate a downloadable template based on the configured fields.
- File upload capability: Users can upload a CSV or Excel file that matches the template format.
- Modal popups for feedback: Display error or success messages in a popup modal for a better user experience.
LWC Meta XML Configuration (dynamicUploadComponent.js-meta.xml)
We define the object name and fields as configurable properties in the meta XML file. These properties will be set when adding the component to a Lightning page.
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>57.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage,lightning__AppPage,lightning__HomePage">
<property name="objectName" type="String" label="Object Name" description="API Name of the Salesforce Object"/>
<property name="fieldNames" type="String" label="Field Names" description="Comma separated API Names of the fields"/>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
LWC HTML Template (dynamicUploadComponent.html)
The HTML template includes a section for displaying object and field information, buttons to download the template, and upload file input. It also includes a modal popup to display messages.
<template>
<lightning-card title="Dynamic File Upload">
<!-- Display configured object name and fields -->
<p class="slds-m-around_medium">Object: {objectName}
</p>
<p class="slds-m-around_medium">Fields: {fieldNames}
</p>
<!-- Download Template Button -->
<lightning-button label="Download Template" onclick={downloadTemplate} class="slds-m-around_medium" disabled={disableTemplateDownload}>
</lightning-button>
<!-- File Upload -->
<lightning-file-upload label="Upload File" accept=".csv,.xlsx" record-id={recordId} onuploadfinished={handleUploadFinished} multiple={false} class="slds-m-around_medium">
</lightning-file-upload>
<!-- Modal Popup for Error and Success Messages -->
<template if:true={showModal}>
<section role="dialog" tabindex="-1" class="slds-modal slds-fade-in-open">
<div class="slds-modal__container">
<header class="slds-modal__header">
<button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse" title="Close" onclick={closeModal}>
<lightning-icon icon-name="utility:close" alternative-text="close" size="small">
</lightning-icon>
<span class="slds-assistive-text">Close
</span>
</button>
<h2 class="slds-text-heading_medium">{modalTitle}
</h2>
</header>
<div class="slds-modal__content slds-p-around_medium">
<p>{modalMessage}
</p>
</div>
<footer class="slds-modal__footer">
<lightning-button variant="neutral" label="Close" onclick={closeModal}>
</lightning-button>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open">
</div>
</template>
</lightning-card>
</template>
LWC JavaScript Controller (dynamicUploadComponent.js)
This script handles the logic for downloading templates, processing uploaded files, and displaying feedback in a modal popup.
import { LightningElement, track, api } from 'lwc';
import { saveAs } from 'file-saver';
import * as XLSX from 'xlsx';
import getObjectFields from '@salesforce/apex/DynamicUploadController.getObjectFields';
import insertRecords from '@salesforce/apex/DynamicUploadController.insertRecords';
export default class DynamicUploadComponent extends LightningElement {
@api objectName; // Object name from meta XML
@api fieldNames; // Field names from meta XML
@track disableTemplateDownload = false; // Enable template download initially
@track showModal = false; // Control the display of the modal
@track modalTitle = ''; // Modal title
@track modalMessage = ''; // Modal message
connectedCallback() {
// Check if objectName and fieldNames are provided
if (!this.objectName || !this.fieldNames) {
this.disableTemplateDownload = true;
this.showModalMessage('Error', 'Object Name and Field Names are required.');
}
}
// Download template based on the object name and field names
downloadTemplate() {
this.showModal = false;
const fieldsArray = this.fieldNames.split(',').map(field => field.trim());
this.generateTemplate(fieldsArray);
}
// Generate CSV/Excel template with field names as header
generateTemplate(fields) {
const csvString = fields.join(',') + '\n';
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, `${this.objectName}_Template.csv`);
}
// Handle file upload and process the data
handleUploadFinished(event) {
this.showModal = false;
const uploadedFiles = event.detail.files;
if (uploadedFiles.length > 0) {
this.processUploadedFile(uploadedFiles[0]);
}
}
// Process uploaded file
processUploadedFile(file) {
const reader = new FileReader();
reader.onload = () => {
const content = reader.result;
const workbook = XLSX.read(content, { type: 'binary' });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const csvData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
if (csvData.length > 1) {
this.uploadData(csvData);
} else {
this.showModalMessage('Error', 'The uploaded file is empty or has no data rows.');
}
};
reader.onerror = () => {
this.showModalMessage('Error', 'Error reading the uploaded file.');
};
reader.readAsBinaryString(file);
}
// Upload data to Salesforce
uploadData(csvData) {
const fields = csvData[0];
const records = [];
for (let i = 1; i < csvData.length; i++) {
let record = {};
for (let j = 0; j < fields.length; j++) {
record[fields[j]] = csvData[i][j] || '';
}
records.push(record);
}
insertRecords({ objectName: this.objectName, records: JSON.stringify(records) })
.then(() => {
this.showModalMessage('Success', 'Records have been successfully uploaded.');
})
.catch(error => {
this.showModalMessage('Error', `Error uploading records: ${error.body.message}`);
});
}
// Show modal with error or success message
showModalMessage(title, message) {
this.modalTitle = title;
this.modalMessage = message;
this.showModal = true;
}
// Close the modal
closeModal() {
this.showModal = false;
}
}
LWC CSS (dynamicUploadComponent.css)
Add styles for the modal popup.
.slds-modal__container { width: 50%; max-width: 50rem; }
.slds-modal__header { display: flex; justify-content: space-between; align-items: center; }
.slds-modal__footer { text-align: right; }
2. Creating the Apex Controller
We use an Apex controller to dynamically retrieve fields for the configured object and to insert records into Salesforce.
Apex Controller (DynamicUploadController.cls)
Java
public with sharing class DynamicUploadController {
@AuraEnabled(cacheable=true)
public static List<String> getObjectFields(String objectName) {
// Retrieve field names for the specified object
try {
Map<String, Schema.SObjectField> fieldsMap = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap();
return new List<String>(fieldsMap.keySet());
} catch (Exception e) {
throw new AuraHandledException('Error fetching fields: ' + e.getMessage());
}
}
@AuraEnabled
public static void insertRecords(String objectName, String records) {
// Insert records into the specified object
if (String.isBlank(objectName) || String.isBlank(records)) {
throw new AuraHandledException('Object Name and Records cannot be empty.');
}
List<SObject> sObjectList = new List<SObject>();
List<Map<String, Object>> recordList = (List<Map<String, Object>>) JSON.deserializeUntyped(records);
for (Map<String, Object> recordMap : recordList) {
SObject sObjectRecord = (SObject) Schema.getGlobalDescribe().get(objectName).newSObject(null, true);
for (String fieldName : recordMap.keySet()) {
sObjectRecord.put(fieldName, recordMap.get(fieldName));
}
sObjectList.add(sObjectRecord);
}
try {
insert sObjectList;
} catch (Exception e) {
throw new AuraHandledException('Error inserting records: ' + e.getMessage());
}
}
}
public with sharing class DynamicUploadController {
@AuraEnabled(cacheable=true)
public static List<String> getObjectFields(String objectName) {
// Retrieve field names for the specified object
try {
Map<String, Schema.SObjectField> fieldsMap = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap();
return new List<String>(fieldsMap.keySet());
} catch (Exception e) {
throw new AuraHandledException('Error fetching fields: ' + e.getMessage());
}
}
@AuraEnabled
public static void insertRecords(String objectName, String records) {
// Insert records into the specified object
if (String.isBlank(objectName) || String.isBlank(records)) {
throw new AuraHandledException('Object Name and Records cannot be empty.');
}
List<SObject> sObjectList = new List<SObject>();
List<Map<String, Object>> recordList = (List<Map<String, Object>>) JSON.deserializeUntyped(records);
for (Map<String, Object> recordMap : recordList) {
SObject sObjectRecord = (SObject) Schema.getGlobalDescribe().get(objectName).newSObject(null, true);
for (String fieldName : recordMap.keySet()) {
sObjectRecord.put(fieldName, recordMap.get(fieldName));
}
sObjectList.add(sObjectRecord);
}
try {
insert sObjectList;
} catch (Exception e) {
throw new AuraHandledException('Error inserting records: ' + e.getMessage());
}
}
}
3. Using the Component
- Add the component to a Lightning page: Use the App Builder to place the
dynamicUploadComponent
on a Lightning record, app, or home page. - Configure the component: Set the
objectName
andfieldNames
properties in the component configuration. - Download the template: Click the "Download Template" button to get the CSV template for the specified object and fields.
- Upload data: Fill in the template and upload it using the file upload component. The data will be validated and inserted into Salesforce.
Conclusion
Now you have created a flexible and reusable LWC component for data upload with Salesforce. This component guides users through the process with configurable templates, improving the overall user experience. You can further extend this component by adding features such as:
- Advanced data validation: Implement checks for required fields and data formats.
- Customizable modal: Enhance the modal with additional actions or styling options.
This dynamic upload component is a powerful tool for any Salesforce implementation, offering a streamlined solution for data import tasks.
Opinions expressed by DZone contributors are their own.
Comments