Introduction:
In this blog post, we’ll explore how to create a custom list view in Salesforce using Lightning Web Components (LWC) and Apex. This will enable you to fetch, display, and print Task records based on specific filters. We will walk through the Apex controller and LWC code step by step. Additionally, we’ll cover how to create a list layout button, a custom list view, and how to pass the current list view ID to an LWC.
Prerequisites
- Basic understanding of Salesforce development.
- Familiarity with Apex and Lightning Web Components.
- Access to a Salesforce org with appropriate permissions.
Step 1: Create a Visualforce Page
First, we need a Visualforce page to connect to the LWC (ListviewPage).
<apex:page standardController=”Task” recordSetVar=”tasks” extensions=”CustomListViewInLwcCtrl”>
<apex:includeLightning />
<style>
#lightning {
height: 100vh;
}
@media print {
#lightning {
height: auto;
overflow: visible;
}
.print-section {
height: auto;
overflow: visible;
}
}
</style>
<div id=’lightning’></div>
<script>
console.log(‘work6’);
var filterId = ‘{!filterId}’;
console.log(‘Filter ID:’, filterId);
$Lightning.use(
“c:ExampleLWCApp”,
function() {
$Lightning.createComponent(
“c:listviewpage”,
{
‘filterId’: filterId
},
“lightning”
);
}
);
</script>
</apex:page>
Step 2: Create an Aura Component
<aura:application extends=”ltng:outApp” >
<aura:dependency resource=’listviewpage’/>
</aura:application>
Step 3: Create an Apex Controller
First, we need an Apex controller to handle the fetching of list views and their records. Here’s the CustomListViewInLwcCtrl Apex controller:
public with sharing class CustomListViewInLwcCtrl {private String filterId;public CustomListViewInLwcCtrl(ApexPages.StandardSetController controller) {// Assuming you’re passing a filter ID as a parameterfilterId = controller.getFilterId();system.debug(‘FilterId–>’ + filterId);}public String getFilterId() {return filterId;}@AuraEnabled(cacheable = true)public static List<ListView> fetchTaskListView(String objectApiName) {try {return [SELECT Id, Name, DeveloperNameFROM ListViewWHERE SObjectType = :objectApiNameORDER BY DeveloperName ASC];} catch (Exception e) {System.debug(‘Error fetching list views: ‘ + e.getMessage());return new List<ListView>();}}@AuraEnabled(cacheable = true)public static List<sObject> getTaskListviewRecord(String objectName, String listViewId, String limitsize, String offsize) {List<sObject> result = new List<sObject>();try {// Construct endpoint URLString baseUrl = URL.getOrgDomainUrl().toExternalForm();String endPointURL = baseUrl + ‘/services/data/v50.0/sobjects/’ + objectName + ‘/listviews/’ + listViewId + ‘/describe’;// Create HTTP requestHttpRequest req = new HttpRequest();req.setEndpoint(endPointURL);req.setMethod(‘GET’);// Check if session ID is validString sessionId = UserInfo.getSessionId();if (String.isEmpty(sessionId)) {System.debug(‘Invalid session ID’);return result;}req.setHeader(‘Authorization’, ‘Bearer ‘ + sessionId);// Send HTTP requestHttp http = new Http();HttpResponse response = http.send(req);if (response.getStatusCode() == 200) {Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());System.debug(‘View—>’ + responseMap);if (responseMap.containsKey(‘query’)) {String query = (String) responseMap.get(‘query’);// Build the query dynamically with limitsize and offsizequery += ‘ LIMIT ‘ + limitsize + ‘ OFFSET ‘ + offsize;//OFFSET remining record to jumpSystem.debug(‘query==>’ + query);// Execute the queryresult = Database.query(query);System.debug(‘record size–>’ + result.size());} else {System.debug(‘Error: Expected “query” key not found in response.’);}} else {System.debug(‘HTTP Status Code: ‘ + response.getStatusCode());}} catch (Exception e) {System.debug(‘Error fetching list view records: ‘ + e.getMessage());}return result;}@AuraEnabled(cacheable = true)public static List<Map<String, String>> getTaskListviewLabel(String objectName, String listViewId) {List<Map<String, String>> labels = new List<Map<String, String>>();try {// Construct endpoint URLString baseUrl = URL.getOrgDomainUrl().toExternalForm();String endPointURL = baseUrl + ‘/services/data/v50.0/sobjects/’ + objectName + ‘/listviews/’ + listViewId + ‘/describe’;// Create HTTP requestHttpRequest req = new HttpRequest();req.setEndpoint(endPointURL);req.setMethod(‘GET’);// Check if session ID is validString sessionId = UserInfo.getSessionId();if (sessionId == null || sessionId == ”) {system.debug(‘Invalid session ID’);}req.setHeader(‘Authorization’, ‘Bearer ‘ + sessionId);// Send HTTP requestHttp http = new Http();HttpResponse response = http.send(req);if (response.getStatusCode() == 200) {Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());System.debug(‘View—>’ + responseMap);if (responseMap.containsKey(‘columns’)) {List<Object> columns = (List<Object>) responseMap.get(‘columns’);for (Object column : columns) {Map<String, Object> columnMap = (Map<String, Object>) column;Boolean isSortable = (Boolean) columnMap.get(‘sortable’);if (isSortable != null && isSortable) {String fieldNameOrPath = (String) columnMap.get(‘fieldNameOrPath’);String label = (String) columnMap.get(‘label’);Map<String, String> labelInfo = new Map<String, String>();labelInfo.put(‘label’, label);if(fieldNameOrPath==’Who.Name’){labelInfo.put(‘fieldApiName’, ‘WhoId’);}else if(fieldNameOrPath==’What.Name’){labelInfo.put(‘fieldApiName’, ‘WhatId’);}else if(fieldNameOrPath==’Owner.NameOrAlias’){labelInfo.put(‘fieldApiName’, ‘NameOrAlias’);}else if(fieldNameOrPath==’LastModifiedBy.Alias’){labelInfo.put(‘fieldApiName’, ‘LastModifiedBy’);}else if(fieldNameOrPath==’Account__r.Name’){labelInfo.put(‘fieldApiName’, ‘Account__c’);}else if(fieldNameOrPath==’Owner.Name’){labelInfo.put(‘fieldApiName’, ‘Owner’);}else{labelInfo.put(‘fieldApiName’, fieldNameOrPath);}labels.add(labelInfo);system.debug(‘Key Val1’+labels);}}}} else {System.debug(‘HTTP Status Code: ‘ + response.getStatusCode());}} catch (Exception e) {System.debug(‘Error fetching list view labels: ‘ + e.getMessage());}return labels;}}
Step 4: Create a Lightning Web Component
Next, let’s create the Lightning Web Component that will interact with our Apex controller to fetch and display the Task records. Here’s the listviewPage LWC:
HTML Template
<template><div class=”slds-grid slds-wrap” style=”width: 280px;”><div class=”slds-m-around_medium”><lightning-comboboxname=”listViewSelect”label=”Select List View”value={selectedListView}placeholder=”Select a List View”options={listViewOptions}onchange={handleListViewChange}></lightning-combobox></div></div><br><div class=”slds-grid slds-wrap”><!– Image column –><div class=”slds-col slds-size_1-of-4″></div><!– Print button column –><div class=”slds-col slds-size_3-of-4 slds-text-align_right”><lightning-buttonvariant=”brand”label=”Print”onclick={handlePrint}></lightning-button></div></div><br><div if:true={isLoading}><lightning-spinner alternative-text=”Loading”></lightning-spinner></div><template if:false={isLoading}><div class=”print-section”><template if:true={records.length}><lightning-datatablekey-field=”Id”data={records}columns={columns}hide-checkbox-column></lightning-datatable><!– Pagination buttons –><div class=”slds-m-top_medium slds-text-align_center”><lightning-button-group><lightning-button class=”previous”label=”Previous”onclick={handlePrevious}disabled={disablePrevious}></lightning-button><lightning-button class=”next”label=”Next”onclick={handleNext}disabled={disableNext}></lightning-button></lightning-button-group></div></template><template if:false={records.length}><div class=”slds-text-align_center”>No records to display</div></template></div></template></template>
JavaScript Controller
import { LightningElement, track, wire, api } from ‘lwc’;import fetchListView from ‘@salesforce/apex/CustomListViewInLwcCtrl.fetchTaskListView’;import getTaskListviewRecord from ‘@salesforce/apex/CustomListViewInLwcCtrl.getTaskListviewRecord’;import getTaskListviewLabel from ‘@salesforce/apex/CustomListViewInLwcCtrl.getTaskListviewLabel’;const PAGE_SIZE = 100; // Adjust this based on your pagination needsexport default class ListviewPage extends LightningElement {@api filterId;@track listViewOptions = [];@track selectedListView = ”;@track records = [];@track columns = [];@track isLoading = true;@track limitsize = PAGE_SIZE;@track offset = 0;connectedCallback() {console.log(‘Filter ID5:’, this.filterId);}@wire(fetchListView, { objectApiName: ‘Task’ })fetchListViewHandler({ data, error }) {if (data) {this.listViewOptions = data.map(listView => ({label: listView.Name,value: listView.Id}));if (this.filterId) {this.selectedListView = this.filterId;} else {this.selectedListView = this.listViewOptions[0].value;}this.fetchRecords();} else if (error) {console.error(‘Error fetching list views:’, error);}}fetchRecords() {this.isLoading = true;getTaskListviewRecord({objectName: ‘Task’,listViewId: this.selectedListView,limitsize: this.limitsize.toString(),offsize: this.offset.toString() // Pass offset correctly}).then(result => {console.log(result.length + ‘ records’, result);const startIndex = this.offset + 1;this.records = result.map((record, index) => ({…record,Count: startIndex + index,WhatId: record.What ? record.What.Name : ”,WhoId: record.Who ? record.Who.Name : ”,Owner: record.Owner ? record.Owner.Name : ”,Subject: record.Subject ? record.Subject : ”,Status: record.Status ? record.Status : ”,Priority: record.Priority ? record.Priority : ”,NameOrAlias: record.Owner ? record.Owner.NameOrAlias : ”,LastModifiedBy: record.LastModifiedBy ? record.LastModifiedBy.Alias : ”,LastModifiedDate: record.LastModifiedDate ? this.formatDate(record.LastModifiedDate) : ”,TaskDescription: record.Task_Description ? record.Task_Description : ”,Account__c: record.Account__r ? record.Account__r.Name : ”,Inventory__c: record.Inventory__r ? record.Inventory__r.Name : ”,Phone__c: record.Phone__c ? record.Phone__c : ”,Invoice_Follow_Up_Stage__c: record.Invoice_Follow_Up_Stage__c ? record.Invoice_Follow_Up_Stage__c : ”,Terms__c: record.Terms__c ? record.Terms__c : ”,Sage_100_SO_Invoice_Number__c: record.Sage_100_SO_Invoice_Number__c ? record.Sage_100_SO_Invoice_Number__c : ”,Product_Name__c: record.Product_Name__c ? this.parsedValue(record.Product_Name__c) : ”,Production_Type__c: record.Production_Type__c ? record.Production_Type__c : ”,Stock_Number__c: record.Stock_Number__c ? record.Stock_Number__c : ”,Tech_Name__c: record.Tech_Name__c ? record.Tech_Name__c : ”,Priority__c: record.Priority__c ? record.Priority__c : ”,Tube_Tech_Name__c: record.Tube_Tech_Name__c ? record.Tube_Tech_Name__c : ”,RelatedTo_Type__c: record.RelatedTo_Type__c ? record.RelatedTo_Type__c : ”,RecordType: record.RecordType ? record.RecordType.Name : ”,}));this.fetchLabels();}).catch(error => {console.error(‘Error fetching records:’, error);this.records = [];this.isLoading = false;});}parsedValue(htmlString) {const div = document.createElement(‘div’);div.innerHTML = htmlString;const anchorTag = div.querySelector(‘a’);return anchorTag ? anchorTag.textContent : htmlString;}formatDate(dateString) {const options = {month: ‘short’,day: ‘numeric’,year: ‘numeric’,hour: ‘numeric’,minute: ‘2-digit’,hour12: true};return new Date(dateString).toLocaleDateString(‘en-US’, options);}fetchLabels() {getTaskListviewLabel({objectName: ‘Task’,listViewId: this.selectedListView}).then(labels => {console.log(‘Label’, labels);this.columns = [{ label: ‘ ‘, fieldName: ‘Count’, type: ‘number’ }, // Add Count column…labels.map(labelInfo => ({label: labelInfo.label,fieldName: labelInfo.fieldApiName,type: ‘text’}))];this.isLoading = false;}).catch(error => {console.error(‘Error fetching labels:’, error);this.isLoading = false;});}handleListViewChange(event) {this.selectedListView = event.detail.value;this.offset = 0; // Reset offset when list view changesthis.fetchRecords();}handlePrint() {if (confirm(“Are you sure you want to print?”)) {window.print();}}handlePrevious() {if (this.offset >= PAGE_SIZE) {this.offset -= PAGE_SIZE;this.fetchRecords();}}handleNext() {this.offset += PAGE_SIZE;this.fetchRecords();}get disablePrevious() {return this.offset === 0;}get disableNext() {return this.records.length < PAGE_SIZE;}}
PAGE_SIZE;}}
Handle Internal Errors
If an internal error occurs while fetching the records, we will display an appropriate error message to the user. The catch block in the fetchRecords
method handles this by setting the error
property.
Steps 1 Add Remote Site Settings
To allow your Apex class to make callouts, you need to add your Salesforce org’s URL to the Remote Site Settings. Here’s how to do it:
- Navigate to Setup:
- Go to the Setup menu in Salesforce.
- Search for Remote Site Settings:
- In the Quick Find box, type “Remote Site Settings” and select it.
- Create a New Remote Site:
- Click the “New Remote Site” button.
- Fill in the required fields:
- Remote Site Name: Give it a name like “MyOrgSite”.
- Remote Site URL: Enter your Salesforce org URL (e.g.,
https://myorg.my.salesforce.com
).
- Save the settings.
By adding the remote site settings, you ensure that your Salesforce org can make callouts to itself, which is necessary for the Apex class to function correctly.
Creating a List Layout Button
To create a button on a list view that opens our custom list view Visualforce page:
- Go to Salesforce Setup.
- Navigate to Object Manager.
- Select the Task object (or the desired object).
- Under Buttons, Links, and Actions, click New Button or Link.
- Fill in the details:
- Label: Custom List View
- Name: Custom_List_View
- Display Type: List Button
- Behavior: Display in existing window without sidebar or header
- Content Source: URL
- URL: /apex/customListViewPage
- Click Save.
- Add the button to the Search Layouts for the object.
Getting the Current List View ID in LWC
To get the current list view ID in the LWC, you can use the filterId parameter passed from the Visualforce page. This parameter is set in the connectedCallback method and used to fetch the relevant data.
Putting It All Together
- Create the Apex controller, Visualforce page, and LWC component with the functionalities described above.
- Deploy the code to your Salesforce org.
- Create the list layout button and add it to the object.
- Access the Visualforce page from the Lightning Experience.
- You should see a dropdown to select a list view, followed by the corresponding data displayed in a table format.
Final Output
Conclusion
By following these steps, you can create a custom list view in Salesforce using Lightning Web Components and Apex. This solution allows you to fetch and display Task records dynamically and print the list view. Adjust the code as needed to fit your specific requirements.