Lightning tree grid table inside lightning web component

Nov 15, 2024


 

 

A lightning-tree-grid is a table that shows data in a hierarchy. It looks similar to a lightning-datatable, as it uses lightning-datatable internally, but with the added feature that each row can be expanded to show more details. Rows with extra data have a chevron icon, which users can click to expand or collapse the information. This makes it easy to display organized data, like account structures or forecasting data.

Inline editing and sorting of columns are not supported.

Supported features include:

  1. Displaying and formatting of columns with appropriate data types
  2. Header-level actions
  3. Row-level actions
  4. Resizing of columns
  5. Selecting of rows
  6. Text wrapping and clipping

This component provides styling for up to 20 nested levels.

Note: key-field is required.

The expanded-rows attribute is optional, and expands nested items on a row when provided. Selecting a row using the checkbox does not select nested the rows.

Note: Nested items must be defined using the _children key.

The columns, data, and currentExpanded properties are private reactive properties, which is indicated by the @track decorator in the JS file. If the values of any of these properties change, the component's template rerenders.

let's understand with example of Contact and Case object relaitonship.

treeGridTable.html
<template>
    <lightning-card>
        <lightning-layout multiple-rows="true">
            <lightning-layout-item padding="around-small" size="12">
                <div class="slds-float_right">
                    <lightning-button label="Refresh" onclick={refreshData}></lightning-button>
                </div>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small" size="12">
                    <lightning-tree-grid
                    columns={gridColumns}
                    data={gridData}
                    key-field="Id"
                    onrowselection={rowSelection}
                    onrowaction={rowAction}
                    onsort={updateColumnSorting}
                    ></lightning-tree-grid>
            </lightning-layout-item>
        </lightning-layout>

    </lightning-card>
</template>

 

treeGridTable.js
import { LightningElement, wire, track } from 'lwc';
import getContactCase from '@salesforce/apex/treeGridTableController.getContactCase';
import deleteContactOrCase from '@salesforce/apex/treeGridTableController.deleteContactOrCase';
import { refreshApex } from '@salesforce/apex';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { reduceErrors } from 'c/ldsUtils';
import { NavigationMixin } from 'lightning/navigation';
import LightningConfirm from 'lightning/confirm';

const actions = [
    { label: 'Edit', name: 'edit' },
    { label: 'Delete', name: 'delete' },
];

const columns = [
    // {label:'Id', fieldName:'Id', sortable:true},
    {label:'Name', fieldName: 'Name'},
    {label:'Department', fieldName: 'Department'},
    {label:'Title', fieldName: 'Title'},
    {label:'Action', type: 'action', typeAttributes: { rowActions: actions }}
]

export default class GridTable extends NavigationMixin(LightningElement) {

    @track gridColumns = columns;
    @track gridData;

    wiredActivities;
    @wire(getContactCase)
    wireGetContactCase(value) {
        this.wiredActivities = value;
        const {data, error} = value;
        if(data) {
            this.gridData = data.map((contact, ind) => {
                return {
                    Id: contact.Id,
                    Name: contact.Name,
                    Department: contact.Department,
                    Title:contact.Title,
                    _children:[{"id":"Id", "casenumber":"CaseNumber", "priority":"Priority", "subject":"Subject"}].map((datafield) => {
                        return {
                            Id: `${contact.Id}-child-1`,
                            Department: datafield.casenumber,
                            Name: datafield.priority,
                            Title: datafield.subject,
                            _children: contact.Cases != undefined && contact.Cases.map((cas) => {
                                return {
                                    Id: `${cas.Id}`,
                                    Department: cas.CaseNumber,
                                    Name: cas.Priority,
                                    Title: cas.Subject
                                }
                            })
                        }
                    })
                };
            })
  
           console.log("griddata => "+ JSON.stringify(this.gridData));
        } else if(error) {
            this.showToast('Error', error.body.message, 'error');
        }
    }

    rowSelection(event) {
        const row = event.detail.selectedRows;
        console.log(JSON.stringify(row));
    }

    rowAction(event) {
        const actionName = event.detail.action.name;
        const row = event.detail.row;

        switch (actionName) {
            case 'edit':
                this.editRow(row);
                break;
            case 'delete':
                this.deleteRow(row);
                break;
            default:
        }
    }

    editRow(row) {
        console.log("Edit row => "+ JSON.stringify(row));
        const idStartWith = row.Id.substring(0, 3);
        let rowId=null;
        let objectName=null;
        if(row.Name != 'Priority') {
            if(idStartWith == '003') {
                rowId = row.Id;
                objectName = 'Contact';
            }
            if(idStartWith == '500') {
                rowId = row.Id.substring(0, 18);
                objectName = 'Case';
            }
            this[NavigationMixin.Navigate]({
                type: 'standard__recordPage',
                attributes: {
                    recordId: rowId,
                    objectApiName: objectName,
                    actionName: 'edit'
                },
            })
        } else {
            this.showToast("Info", "Please select record", "info");
        }
    }

    async deleteRow(row) {
        const idStartWith = row.Id.substring(0, 3);
        let rowId=null;
        let objectName=null;
        if(row.Name != 'Priority') {
            if(idStartWith == '003') {
                rowId = row.Id;
                objectName = 'Contact';
            }
            if(idStartWith == '500') {
                rowId = row.Id.substring(0, 18);
                objectName = 'Case';
            }
            try {
 
                const response  = await this.handleConfirmClick(objectName);
                if(response) {
                    const response  = await this.handleConfirmClick(objectName);
                    if(response) {
                        console.log('recordId: '+ rowId);
                        await deleteContactOrCase({recordId: rowId, objectName: objectName})
                        refreshApex(this.wiredActivities);
                        this.showToast('Success', `${objectName} deleted`, 'success');
                }
            } catch (error) {
                this.showToast('Error', reduceErrors(error).join(', '), 'error');
            }
        } else {
            this.showToast("Info", "Please select record", "info");
        }
    }

    async handleConfirmClick(objectName) {
        const result = await LightningConfirm.open({
            message: `Are you sure you want to delete this ${objectName}?`,
            variant: 'header',
            label: `Delete ${objectName}`,
            theme: 'error'
        });
        return result;
    }

    refreshData() {
        refreshApex(this.wiredActivities);
    }

    showToast(title, msg, error) {
        const toastEvent = new ShowToastEvent({
            title: title,
            message: msg,
            variant: error,
            mode: 'dismissible'
        })
        this.dispatchEvent(toastEvent);
    }
}

 

treeGridTable.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

 

treeGridTableController.apxc
public with sharing class treeGridTableController {
 
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContactCase(){
        try {
            //List<Contact> contactCaseList = [SELECT Id, Name, Department, Title, (SELECT Id, CaseNumber, Priority, Subject FROM Cases) FROM Contact WHERE Id IN (SELECT ContactId FROM Case)];
            List<Contact> contactCaseList = [SELECT Id, Name, Department, Title, (SELECT Id, CaseNumber, Priority, Subject FROM Cases) FROM Contact ORDER BY CreatedDate DESC];
            return contactCaseList;
        } catch (Exception e) {
            throw new AuraHandledException(e.getMessage());
        }
    }

    @AuraEnabled
    public static void deleteContactOrCase(String recordId, String objectName){
        System.debug(recordId+' '+objectName);
        if(objectName=='Contact') {
            List<Contact> con = [SELECT Id, Name FROM Contact WHERE Id =: recordId];
            try {
                System.debug('Contact => '+ con);
                delete con;
            } catch (DmlException e) {
                throw new AuraHandledException(e.getMessage());
            }
        }
        else if(objectName=='Case') {
            List<Case> cas = [SELECT Id, Subject FROM Case WHERE Id =: recordId];
            try {
                System.debug('Case => '+ cas);
                delete cas;
            } catch (DmlException e) {
                throw new AuraHandledException(e.getMessage());
            }
        } else {
           
        }
    }
}
  • Connect with us

  • sales@phenoble.com

  • hr@phenoble.com

  • +91 7850952954


© 2025 Phenoble Software Private Limited