LWC DataTable With Dynamic Columns Controlled By Custom Metadata

Telegram logo Join our Telegram Channel

Welcome Trailblazers! In this tutorial, I will guide you through the process of creating a dynamic Lightning Datatable managed through custom metadata.


Scenario

  1. Enable administrators to configure column properties using custom metadata.
  2. Query records based on the defined column properties in the custom metadata.
  3. Generate a Lightning Datatable dynamically based on the custom metadata.
  4. Admin should be able to add/remove columns from setup by adjusting custom metadata records.
  5. Admin should be able to reorder the columns setup by adjusting custom metadata records.

Custom Metadata Fields

Before delving into the code, let's establish a custom metadata object to store column information. I've already set up a custom metadata object containing the following fields:

Datatable_Column__mdt (custom metadata)

  1. Field_Api__c: API name of the field to be displayed in the column (required).
  2. Field_Label__c: Label to be shown on the column (required).
  3. Group__c: Group of columns; for instance, fields of the Opportunity object can be grouped together. Multiple unique groups can be created for different scenarios involving the same object (required).
  4. Initial_Width__c: Initial width of the column (optional).
  5. Data_Type__c: Datatype of the column (optional; set to text if not provided).
  6. Custom_Type_Name__c: Name of the custom datatype if using a custom column type (required when Data_Type__c is set as custom).
  7. Order__c: Sort order for the column, left to right. This field will allow the admin to change the column display order.
  8. Additional_Props_JSON__c: JSON value for additional column properties. For example, you can provide the type attributes of the column.


Custom Metadata Records Screenshot

See the sample columns I have saved in the custom metadata.
LWC DataTable With Dynamic Columns - custom metadata records


Apex Controller

The Apex controller facilitates the logic for managing dynamic columns and fetching records based on custom metadata. Let us understand the code:

  • getColumnMetadata Method:
    • Fetches dynamic column metadata information from a custom metadata type (Datatable_Column__mdt).
    • Takes a groupName parameter to filter columns based on a specified group name.
    • Returns a list of Datatable_Column__mdt records ordered by the Order__c field.
  • getOpportunitiesWithColumns Method:
    • Utilizes getColumnMetadata to retrieve column configurations for the 'Opportunity' group.
    • Builds a dynamic SOQL query based on the retrieved columns.
    • Executes the query to retrieve Opportunity records.
    • Constructs a wrapper class (DatableConfigWrapper) containing both the data (Opportunities) and column configurations for the Lightning datatable.
    • Returns the wrapper class.
  • DatableConfigWrapper Class:
    • A wrapper class to hold both the data and column configurations for the Lightning datatable.
    • Consists of two public properties: data (List of Opportunities) and columnsInfo (List of Datatable_Column__mdt).
  • isFieldValid Method:
    • Checks if a field exists on a specified object by utilizing the Salesforce Schema class.


DynamicColumnsDatatableCtrl 

/**
 * @description - Sample code for Lightning Datatable with dynamic columns. Columns config is stored in custom metadata.
 * @author Rahul Gawale
 * @createdAt - 2024-01-27
 */

public with sharing class DynamicColumnsDatatableCtrl {
    /**
     * @description - this function fetches the dynamic column metadata information
     * @param groupName - group name of the fields to display.
     * @return - Returns list of Datatable_Column__mdt records with specified groupName.
     */
    public static List<Datatable_Column__mdt> getColumnMetadata(
        String groupName
    ) {
        return [
            SELECT
                Field_Label__c,
                Field_Api__c,
                Data_Type__c,
                Initial_Width__c,
                Custom_Type_Name__c,
                Additional_Props_JSON__c,
                Order__c
            FROM Datatable_Column__mdt
            WHERE Group__c = :groupName
            ORDER BY Order__c
        ];
    }

    /**
     * @description - fetch opportunities to show in the datatable.
     */
    @AuraEnabled(cacheable=true)
    public static DatableConfigWrapper getOpportunitiesWithColumns() {
        // get the column config.
        List<Datatable_Column__mdt> columns = getColumnMetadata('Opportunity');

        List<String> fields = new List<String>();
        for (Datatable_Column__mdt col : columns) {
            // Validate if the field API name is not null or empty
            if (
                String.isNotBlank(col.Field_Api__c) &&
                isFieldValid('Opportunity', col.Field_Api__c.trim())
            ) {
                // Add the field API name to the list
                fields.add(col.Field_Api__c);
            }
        }

        String query = 'SELECT ';
        if (fields.isEmpty()) {
            // If no valid fields found, query all fields
            query += 'Id, Name, StageName, CloseDate FROM Opportunity';
        } else {
            // Build the query using the valid fields
            query += String.join(fields, ', ') + ' FROM Opportunity';
        }

        query += ' LIMIT 10';

        // Add any additional conditions or order by clauses as needed
        // For example: query += ' WHERE StageName = \'Closed\' ORDER BY CloseDate DESC';

        // Execute the query and return the results
        List<Opportunity> opportunities = Database.query(query);

        DatableConfigWrapper wrapper = new DatableConfigWrapper();
        wrapper.data = opportunities;
        wrapper.columnsInfo = columns;
        return wrapper;
    }

    /**
     * @description wrapper for returning data and column config for Lightning datatable.
     */
    public class DatableConfigWrapper {
        @AuraEnabled
        public List<SObject> data { get; set; }
        @AuraEnabled
        public List<Datatable_Column__mdt> columnsInfo { get; set; }
    }

    /**
     * @description - Check if a field exists on an object.
     * @param objectApiName - API name of the object.
     * @param fieldApiName - API name of the field.
     * @return - Returns true if the field exists, otherwise false.
     */
    private static Boolean isFieldValid(
        String objectApiName,
        String fieldApiName
    ) {
        Map<String, Schema.SObjectField> fieldMap = Schema.getGlobalDescribe()
            .get(objectApiName)
            .getDescribe()
            .fields.getMap();
        return fieldMap.containsKey(fieldApiName);
    }
}


Lightning Web Component (LWC)

  • Import Statements:
    • Imports necessary modules (LightningElement, wire, track) and the Apex method (getOpportunitiesWithColumns).
  • Component Class (DynamicColumnsDatatable):
    • Declares tracked properties for columns (columns), data (opportunities), and loading status (isLoading).
  • getOpportunitiesWithColumnsWired Wire Method:
    • Uses the @wire decorator to call the getOpportunitiesWithColumns Apex method.
    • Handles the response, populating the columns.

I've consolidated all the code within this Git repository, encompassing metadata files such as Apex, LWC, the definition of the custom metadata object, and records for the custom metadata object.


DynamicColumnsDatatable.js

import { LightningElement, wire, track } from "lwc";

import getOpportunitiesWithColumns from "@salesforce/apex/DynamicColumnsDatatableCtrl.getOpportunitiesWithColumns";

export default class DynamicColumnsDatatable extends LightningElement {
    // columns config for datatable
    @track columns = [];

    // data
    @track opportunities = [];

    @wire(getOpportunitiesWithColumns)
    getOpportunitiesWithColumnsWired({ error, data }) {
        if (error) {
            console.error(
                "Error getting opportunities and column config",
                error
            );
        } else if (data) {
            console.log("columnsInfo", JSON.stringify(data.columnsInfo));
            this.columns = data.columnsInfo.map((col) => ({
                label: col.Field_Label__c,
                fieldName: col.Field_Api__c,
                type: this.getDataType(
                    col.Data_Type__c,
                    col.Custom_Type_Name__c
                ),
                initialWidth: col.Initial_Width__c,
                // if any additional column info provided in JSON.
                ...this.getAdditionalColumnInfo(col.Additional_Props_JSON__c)
            }));

            this.opportunities = data.data;
        }
    }

    getAdditionalColumnInfo(json) {
        if (!json) return {};
        try {
            return JSON.parse(json);
        } catch (e) {
            console.error("error while parsing additional column info", e);
            return {};
        }
    }

    getDataType(type, custom) {
        if (!type) return "text";
        if (type === "custom") {
            return custom;
        }
        return type;
    }
}


dynamicColumnsDatatable.html

<!-- sldsValidatorIgnore -->
<template>
    <lightning-card
        icon-name="standard:portal_roles_and_subordinates"
        variant="narrow"
    >
        <div slot="title">Dynamic Column Data Table</div>
        <div slot="actions"></div>

        <div slot="footer"></div>
        <div>
            <lightning-datatable
                key-field="Id"
                data={opportunities}
                columns={columns}
            ></lightning-datatable>
        </div>
    </lightning-card>
</template>


dynamicColumnsDatatable.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>59.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Dynamic Column Datatable </masterLabel>
    <description>Datatable with dynamic columns stored to custom metadata</description>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>


Output

LWC DataTable With Dynamic Columns



Closing thoughts

As you can see how easy it is to achieve lightning datatable that is controlled through custom metadata records. I hope this was helpful and it motivates you to build something similar.

I've consolidated all the code within this Git repository: dynamic-columns-datatable, encompassing metadata files such as Apex, LWC, the definition of the custom metadata object, and records for the custom metadata object.

  • This is just a proof of concept and you can add your own features to this Dynamic Column Lightning Datatable controlled from Custom Metadata.
  • Feel free to raise pull requests on the GitHub repo mentioned above.


No comments :
Post a Comment

Hi there, comments on this site are moderated, you might need to wait until your comment is published. Spam and promotions will be deleted. Sorry for the inconvenience but we have moderated the comments for the safety of this website users. If you have any concern, or if you are not able to comment for some reason, email us at rahul@forcetrails.com