Creating a Custom List View in Salesforce Using LWC and Apex

In this blog post, we’ll guide you through the process of creating 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’ll cover the step-by-step development of the Apex controller, LWC component, and the creation of a list layout button to enhance your Salesforce interface.

Thank you for reading this post, don't forget to subscribe!

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

Start by creating a Visualforce page that connects to the LWC, named ListviewPage.

htmlCopy code<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

htmlCopy code<aura:application extends="ltng:outApp">
    <aura:dependency resource="listviewpage" />
</aura:application>

Step 3: Create an Apex Controller

Next, you’ll need an Apex controller to manage the fetching of list views and their associated records.

apexCopy codepublic with sharing class CustomListViewInLwcCtrl {
    private String filterId;

    public CustomListViewInLwcCtrl(ApexPages.StandardSetController controller) {
        filterId = 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, DeveloperName
                FROM ListView
                WHERE SObjectType = :objectApiName
                ORDER 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) {
        // Logic to fetch Task records
    }

    @AuraEnabled(cacheable = true)
    public static List<Map<String, String>> getTaskListviewLabel(String objectName, String listViewId) {
        // Logic to fetch Task record labels
    }
}

Step 4: Create a Lightning Web Component

Create the LWC listviewPage that will interact with the Apex controller.

HTML Template

htmlCopy code<template>
    <div class="slds-grid slds-wrap" style="width: 280px;">
        <div class="slds-m-around_medium">
            <lightning-combobox
                name="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">
        <div class="slds-col slds-size_1-of-4"></div>
        <div class="slds-col slds-size_3-of-4 slds-text-align_right">
            <lightning-button variant="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-datatable key-field="Id" data={records} columns={columns} hide-checkbox-column></lightning-datatable>
                <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

javascriptCopy codeimport { 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;

export 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 ID:', this.filterId);
    }

    @wire(fetchListView, { objectApiName: 'Task' })
    fetchListViewHandler({ data, error }) {
        if (data) {
            this.listViewOptions = data.map(listView => ({
                label: listView.Name,
                value: listView.Id
            }));
            this.selectedListView = this.filterId || 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()
        })
        .then(result => {
            console.log(`${result.length} records`, result);
            this.records = this.formatRecords(result);
            this.fetchLabels();
        })
        .catch(error => {
            console.error('Error fetching records:', error);
            this.records = [];
            this.isLoading = false;
        });
    }

    formatRecords(records) {
        return records.map((record, index) => ({
            ...record,
            Count: this.offset + index + 1,
            // Additional field mappings
        }));
    }

    fetchLabels() {
        getTaskListviewLabel({
            objectName: 'Task',
            listViewId: this.selectedListView
        })
        .then(labels => {
            this.columns = [
                { label: ' ', fieldName: 'Count', type: 'number' },
                ...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;
        this.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;
    }
}

Step 5: Handle Remote Site Settings

To allow your Apex class to make callouts, add your Salesforce org’s URL to the Remote Site Settings:

  1. Navigate to Setup.
  2. Search for “Remote Site Settings” in the Quick Find box.
  3. Click “New Remote Site.”
  4. Fill in the required fields (e.g., Remote Site Name: MyOrgSite, Remote Site URL: https://myorg.my.salesforce.com).
  5. Save the settings.

Step 6: Create a List Layout Button

To create a button that opens your custom list view Visualforce page:

  1. Go to Salesforce Setup.
  2. Navigate to Object Manager and select the Task object.
  3. Go to Buttons, Links, and Actions.
  4. Click “New Button or Link.”
  5. Choose the appropriate settings:
    • Label: Custom List View
    • Name: Custom_List_View
    • Display Type: Detail Page Button
    • Behavior: Display in a new window
    • Content Source: URL
  6. Use the following URL as the content source: /apex/ListviewPage?filterId={!Task.FilterId}

Conclusion

This custom list view component in Salesforce allows for enhanced record handling, display, and printing, offering greater flexibility than the standard list views. By leveraging LWC and Apex, you can create a tailored experience for your users, improving their efficiency and overall satisfaction.

If this was tl;dr, contact Tectonic for assistance today.

Related Posts
Salesforce OEM AppExchange
Salesforce OEM AppExchange

Expanding its reach beyond CRM, Salesforce.com has launched a new service called AppExchange OEM Edition, aimed at non-CRM service providers. Read more

The Salesforce Story
The Salesforce Story

In Marc Benioff's own words How did salesforce.com grow from a start up in a rented apartment into the world's Read more

Salesforce Jigsaw
Salesforce Jigsaw

Salesforce.com, a prominent figure in cloud computing, has finalized a deal to acquire Jigsaw, a wiki-style business contact database, for Read more

Health Cloud Brings Healthcare Transformation
Health Cloud Brings Healthcare Transformation

Following swiftly after last week's successful launch of Financial Services Cloud, Salesforce has announced the second installment in its series Read more