Header Ads Widget

Responsive Advertisement

Building a Generic Lightning Web Component to Display Record Details with Dynamic Layout Sections

How to Build a Versatile Lightning Web Component (LWC) for Dynamic Record Display in Salesforce Salesforce Development with LWC

How to Build a Versatile Lightning Web Component (LWC) for Dynamic Record Display in Salesforce

Are you looking to create a flexible and reusable Lightning Web Component (LWC) in Salesforce that can dynamically display record details for any object type? This blog post will guide you through building a powerful LWC that fetches and displays record data organized into layout sections, similar to Salesforce's standard page layouts. This approach ensures that the component can adapt to any object, providing a highly customizable and user-friendly solution.

Why Build a Generic LWC for Record Display?

Salesforce developers often need to create components that can work across multiple objects without hardcoding details for each specific object. A generic LWC can dynamically handle different object types and layouts, making it a highly efficient tool in your Salesforce toolkit. By leveraging Salesforce's getRecordUi wire adapter, we can fetch both the layout and field details dynamically, ensuring maximum flexibility and reusability.

Step-by-Step Guide to Building the Component

Step 1: Create the LWC Component

First, create a new LWC component named recordDetailLayout. The file structure for this component should look like this:


lwc/
└── recordDetailLayout/
    ├── recordDetailLayout.html
    ├── recordDetailLayout.js
    └── recordDetailLayout.js-meta.xml

Step 2: Implement the Code

We'll need to create three files: recordDetailLayout.html, recordDetailLayout.js, and recordDetailLayout.js-meta.xml. Let's dive into each file.

recordDetailLayout.html

This HTML file defines the component's layout using lightning-accordion to display sections dynamically.

<template>
    <lightning-card title="Record Details" icon-name="standard:record">
        <template if:true={sectionsAvailable}>
            <lightning-accordion allow-multiple-sections-open active-section-name={activeSections}>
                <template for:each={sections} for:item="section">
                    <lightning-accordion-section label={section.heading} key={section.id} name={section.id}>
                        <div class="slds-grid slds-wrap slds-gutters">
                            <template for:each={section.layoutRows} for:item="row">
                                <template for:each={row.layoutItems} for:item="item">
                                    <div key={item.key} class="slds-col slds-size_1-of-2 slds-p-around_x-small">
                                        <div class="slds-form-element">
                                            <label class="slds-form-element__label">{item.fieldLabel}</label>
                                            <div class="slds-form-element__control">
                                                <lightning-formatted-text value={item.fieldValue}></lightning-formatted-text>
                                            </div>
                                        </div>
                                    </div>
                                </template>
                            </template>
                        </div>
                    </lightning-accordion-section>
                </template>
            </lightning-accordion>
        </template>
        <template if:true={error}>
            <p class="slds-text-color_error slds-p-around_medium">{error}</p>
        </template>
    </lightning-card>
</template>

recordDetailLayout.js

This JavaScript file contains the logic for fetching record and layout data. It dynamically handles different objects by using the getRecordUi wire adapter.

import { LightningElement, api, wire, track } from 'lwc';
import { getRecordUi } from 'lightning/uiRecordApi';

export default class RecordDetailLayout extends LightningElement {
    @api recordId;
    @api objectApiName;
    @track sections = [];
    @track sectionsAvailable = false;
    activeSections = [];
    error;

    @wire(getRecordUi, { recordIds: '$recordId', layoutTypes: ['Full'], modes: ['View'] })
    wiredRecordUI({ error, data }) {
        if (data) {
            try {
                const objectLayouts = data.layouts?.[this.objectApiName];
                const records = data.records?.[this.recordId]?.fields;

                if (objectLayouts && records) {
                    const layoutId = Object.keys(objectLayouts)[0];
                    const layoutData = objectLayouts[layoutId];

                    if (layoutData?.Full?.View?.sections) {
                        this.sectionsAvailable = true;
                        this.sections = layoutData.Full.View.sections.map((section, sectionIndex) => ({
                            id: section.id,
                            heading: section.heading,
                            layoutRows: section.layoutRows.map((row, rowIndex) => ({
                                key: `row-${sectionIndex}-${rowIndex}`,
                                layoutItems: row.layoutItems.map((item, itemIndex) => {
                                    const fieldApiName = item.layoutComponents.find(component => component.apiName)?.apiName;
                                    const fieldLabel = item.label || '';
                                    const fieldValue = fieldApiName ? records[fieldApiName]?.value : null;

                                    return {
                                        key: `item-${sectionIndex}-${rowIndex}-${itemIndex}`,
                                        fieldLabel: fieldLabel,
                                        fieldValue: fieldValue
                                    };
                                })
                            }))
                        }));

                        // Set active sections to open the first two sections by default
                        this.activeSections = this.sections.slice(0, 2).map(section => section.id);
                    } else {
                        throw new Error('Full or View layout structure is missing.');
                    }
                } else {
                    throw new Error('No layout or records found for the specified object API name and record ID.');
                }
            } catch (e) {
                console.error('Error processing layout data:', e);
                this.error = 'An error occurred while processing the layout data.';
            }
        } else if (error) {
            console.error('Error fetching record layout data:', error);
            this.error = 'Failed to retrieve record layout information.';
        }
    }
}

recordDetailLayout.js-meta.xml

This metadata file configures the component’s API version and availability in different contexts.

<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>Record Detail Layout</masterLabel>
    <apiVersion>60.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__RecordHome</target>
        <target>lightning__FlowScreen</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen">
            <property name="recordId" type="String" label="Record Id"/>
            <property name="objectApiName" type="String" label="Object API Name"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Salesforce Account Page Layout Example

Below is an example of a Salesforce Account page layout displayed in a modal. This layout shows various fields and sections relevant to account management.

Salesforce Account Page Layout

How It Works

The component utilizes Salesforce's getRecordUi wire adapter to fetch both the layout metadata and the record data dynamically. The fetched layout sections are then displayed using a lightning-accordion, allowing users to easily navigate through different sections of a record.

Key Features:

  • Dynamic Layout Handling: The component adapts to any Salesforce object by fetching layout and field details dynamically based on the objectApiName and recordId properties.
  • User-Friendly Interface: Displays record details in expandable sections, providing a clean and organized view.
  • Error Handling: Robust error handling to manage scenarios where data retrieval fails or layout structures are missing.

Conclusion

By following this guide, you can create a versatile Lightning Web Component that dynamically displays record details for any Salesforce object. This generic LWC is not only flexible and reusable but also enhances user experience by organizing information into collapsible sections.

Feel free to customize this component further based on your specific needs or reach out if you have any questions. Happy coding!

Post a Comment

0 Comments