Thursday, November 15, 2018

Lightning Treegrid : Generic Component to display hierarchy in tabular format for any sobject

In this blog, I am going to share a reusable component which can be used to display hierarchy of records along with other fields information in treegrid format.

Previously we have to use jquery to implement this but now "Lightning:treegrid" tag have been introduced which can be used to display treegrid in lightning framework.

In order to use this component, you have to pass below parameters to this components:
  • ltngcurrentRecId =  RecordId (15 or 18 digit)
  • ltngSobjectName = Object API Name
  • ltngParentFieldAPIName = Parent Field API name which create self relationship
  • ltngColumnLabelList = Specify the column label
  • ltngColumnAPINameList = Specify the API field names in same order as that of column
  • ltngHyperlinkColumn = Field API Name which will work as hyperlink for record detail page
Suppose you have display treegrid for Account hierarchy by using below syntax:

<c:SK_GenericTreeGridCmp ltngcurrentRecId="0019000000ld4kS"
ltngSobjectName="Account"
ltngParentFieldAPIName="ParentId"
ltngColumnLabelList="['Name','Type','Industry','Account Owner']"
ltngColumnAPINameList="['Name','Type','Industry','Owner.Name']"
ltngHyperlinkColumn="Name"/>

Below is output:

Suppose you have to display case hierarchy, then use below code snippet:

<c:SK_GenericTreeGridCmp ltngcurrentRecId="5009000000GJkJE"
ltngSobjectName="Case"
ltngParentFieldAPIName="ParentId"
ltngColumnLabelList="['CaseNumber','Subject','Status','Case Owner']"
ltngColumnAPINameList="['CaseNumber','Subject','Status','Owner.Name']"
ltngHyperlinkColumn="CaseNumber"
ltngHeaderValue="Case Hierarchy"/>

Below is output snapshot


Below is complete code snippet for your reference:

<aura:component controller="SK_GenericTreeGridController">
<aura:attribute name="ltngcurrentRecId" type="String" required="true"/>
<aura:attribute name="ltngSobjectName" type="String" required="true"/>
<aura:attribute name="ltngParentFieldAPIName" type="String" required="true"/>
<aura:attribute name="ltngColumnLabelList" type="List" required="true"
description="provide comma seperated values"/>
<aura:attribute name="ltngColumnAPINameList" type="List" required="true"
description="provide comma seperated values"/>
<aura:attribute name="ltngHyperlinkColumn" type="String"/>
<aura:attribute name="items" type="Object"/>
<aura:attribute name="gridColumns" type="list" />
<aura:attribute name="gridData" type="Object" />
<aura:attribute name="gridExpandedRows" type="List" access="PRIVATE" />
<aura:attribute name="ltngHeaderValue" type="string" default="Hierarchy Using Lightning Tree Grid"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<div class="slds-grid slds-wrap" style="background-color:#E6E6FA;border-style: solid;margin:2%;padding:2%;">
<div class="slds-col slds-size_1-of-1" >
<b>{!v.ltngHeaderValue}</b><br/>
<lightning:treeGrid columns="{! v.gridColumns }"
data="{! v.gridData }"
keyField="Id"
expandedRows="{! v.gridExpandedRows}"
aura:id="mytree" />
</div>
</div>
</aura:component>
({
doInit : function(component, event, helper) {
var action= component.get("c.findHierarchyData");
var parentFieldName = component.get("v.ltngParentFieldAPIName");
var fieldList = component.get("v.ltngColumnAPINameList");
var params={
"recId" :component.get("v.ltngcurrentRecId"),
"parentFieldAPIName":component.get("v.ltngParentFieldAPIName"),
"objectAPIname" : component.get("v.ltngSobjectName"),
"columnLabelList" :component.get("v.ltngColumnLabelList"),
"fieldAPINameList" :component.get("v.ltngColumnAPINameList"),
"hyperlinkColumn" :component.get("v.ltngHyperlinkColumn")
};
if(params){
action.setParams(params);
}
//console.log('****param to controller:'+JSON.stringify(params));
action.setCallback(this, function(response) {
var state = response.getState();
if (state === "SUCCESS") {
var apexResponse = response.getReturnValue();
//console.log(JSON.stringify(apexResponse));
var columns = apexResponse.headerList;
console.log('***columns:'+JSON.stringify(columns));
component.set('v.gridColumns', columns);
var expandedRows = [];
var hierarchydata = apexResponse.recordList;
var roles = {};
//console.log('*******hierarchydata:'+JSON.stringify(hierarchydata));
var results = hierarchydata;
roles[undefined] = { Name: "Root", _children: [] };
hierarchydata.forEach(function(v) {
expandedRows.push(v.Id);
var recordDetail = {};
fieldList.forEach(function(fieldAPIName) {
if(fieldAPIName.includes(".")){
var fname= fieldAPIName;
var ss= fname.split(".");
//console.log('****ss.length:'+ss.length);
var tempValue=v[ss[0]];
//console.log('****initial tempValue:'+JSON.stringify(tempValue));
for(var i=1;i<ss.length;i++){
console.log('****tempValue for '+fname+':'+tempValue[ss[i]]);
tempValue = tempValue[ss[i]];
}
recordDetail[fname]=tempValue;
}else{
recordDetail[fieldAPIName]=v[fieldAPIName];
}
});
recordDetail["Id"]=v["Id"];
recordDetail["RecordURL"]= '/'+v["Id"];
recordDetail["_children"]= [];
console.log('****recordDetail:'+JSON.stringify(recordDetail));
roles[v.Id] = recordDetail;
});
hierarchydata.forEach(function(v) {
roles[v[parentFieldName]]._children.push(roles[v["Id"]]);
});
component.set("v.gridData", roles[undefined]._children);
//console.log('*******treegrid data:'+JSON.stringify(roles[undefined]._children));
component.set('v.gridExpandedRows', expandedRows);
}else if(state === "ERROR"){
var errors = response.getError();
console.error(errors);
alert('Problem with connection.'+errors);
}
});
$A.enqueueAction(action);
}
})
//Author- Sunil02kumar@gmail.com
public class SK_GenericTreeGridController {
@AuraEnabled
public static dataWrapper findHierarchyData(string recId,string parentFieldAPIName,
string objectAPIname,List<string> columnLabelList,
List<string> fieldAPINameList,string hyperlinkColumn){
dataWrapper returnData = new dataWrapper();
try{
if(fieldAPINameList.size()>0 && columnLabelList.size()>0 && columnLabelList.size()==fieldAPINameList.size()){
integer indexCount=0;
List<columnsHeaderWrapper> headerdata = new List<columnsHeaderWrapper>();
for(string ss : fieldAPINameList){
if(ss != null && ss != ''){
columnsHeaderWrapper headerinfo= new columnsHeaderWrapper();
if(ss.equalsignorecase(hyperlinkColumn)){
headerinfo.type='url';
headerinfo.fieldName='RecordURL';
headerInfo.label=columnLabelList[indexCount];
headerInfo.typeAttributes.label.fieldName=ss;
}else{
headerinfo.type='text';
headerinfo.fieldName=ss;
headerInfo.label=columnLabelList[indexCount];
}
headerdata.add(headerInfo);
}
indexCount++;
}
returnData.headerList =headerdata;
}
string queryString = 'select id,ParentId ,'+string.join(fieldAPINameList,',')+' from '+objectAPIname;
//Section to get all child account details from ultimate parent starts-------------------------
List<String> currentParent = new List<String>{};
Integer level = 0;
Boolean endOfStructure = false;
//method to find ultimate parent of account
string topMostparent = GetUltimateParentId(recId,objectAPIname,parentFieldAPIName );
system.debug('*******topMostparent:'+topMostparent);
currentParent.add(topMostparent);
system.debug('**********topMostparent:'+ currentParent);
//Loop though all children
string finalQueryString = '';
List<sObject> queryOutput = new List<sObject> ();
while ( !endOfStructure ){
if( level == 0 ){
finalQueryString = queryString + ' where id IN : CurrentParent ORDER BY '+parentFieldAPIName+' Limit 1000';
}
else {
finalQueryString = queryString + ' where '+parentFieldAPIName+' IN : CurrentParent ORDER BY '+parentFieldAPIName+' Limit 1000';
}
system.debug('********finalQueryString:'+finalQueryString);
if(finalQueryString != null && finalQueryString !=''){
try{
if(Limits.getLimitQueries()-Limits.getQueries()>0){
queryOutput = database.query(finalQueryString);
system.debug('***hierarchy level:'+level);
}else{
system.debug('****endOfStructure is true as SOQL limit reaches:');
endOfStructure = true;
}
}catch(exception ex){
endOfStructure = true;
}
}
system.debug('**queryOutput size:'+queryOutput);
if( queryOutput.size() == 0 ){
endOfStructure = true;
}
else{
currentParent.clear();
//iterating through query output
for ( Integer i = 0 ; i < queryOutput.size(); i++ ){
string recordId= string.valueof(queryOutput[i].get('Id'));
currentParent.add(recordId);
returnData.recordList.add(queryOutput[i]);
}
}
level++;
}
system.debug('**********returnData.recordList:'+returnData.recordList);
system.debug('**********returnData.headerList:'+returnData.headerList);
}catch(exception ex){
system.debug('***exception:'+ex.getMessage());
}
return returnData;
}
// Find the tom most element in Heirarchy
public static String GetUltimateParentId( string recId, string sObjectName, string parentFieldAPIname ){
Boolean top = false;
while ( !top ) {
string queryString = 'select id , ' +parentFieldAPIname+ ' from '+sObjectName + ' where Id =:recId LIMIT 1';
system.debug('**********queryString GetUltimateParentId:'+queryString);
sobject sb = database.query(queryString);
if ( sb.get(parentFieldAPIname) != null ) {
recId = string.valueof(sb.get(parentFieldAPIname));
}else {
top = true;
}
}
return recId ;
}
public class dataWrapper{
@AuraEnabled
public List<columnsHeaderWrapper> headerList;
@AuraEnabled
public List<sObject> recordList;
public dataWrapper(){
recordList=new List<sObject>();
headerList=new List<columnsHeaderWrapper>();
}
}
public class columnsHeaderWrapper{
@AuraEnabled
public string type;
@AuraEnabled
public string fieldName;
@AuraEnabled
public string label;
@AuraEnabled
public TypeAttributes typeAttributes;
public columnsHeaderWrapper(){
typeAttributes=new TypeAttributes();
}
}
public class TypeAttributes {
@AuraEnabled
public Label label;
public TypeAttributes(){
label=new Label();
}
}
public class Label {
@AuraEnabled
public String fieldName ;
}
}
<aura:application extends="force:slds">
<c:SK_GenericTreeGridCmp ltngcurrentRecId="0019000000ld4kS"
ltngSobjectName="Account"
ltngParentFieldAPIName="ParentId"
ltngColumnLabelList="['Name','Type','Industry','Account Owner']"
ltngColumnAPINameList="['Name','Type','Industry','Owner.Name']"
ltngHyperlinkColumn="Name"/>
<c:SK_GenericTreeGridCmp ltngcurrentRecId="5009000000GJkJE"
ltngSobjectName="Case"
ltngParentFieldAPIName="ParentId"
ltngColumnLabelList="['CaseNumber','Subject','Status','Case Owner','Case Owner Email','Account Owner']"
ltngColumnAPINameList="['CaseNumber','Subject','Status','Owner.Name','Owner.Email','Account.Owner.Name']"
ltngHyperlinkColumn="CaseNumber"
ltngHeaderValue="Case Hierarchy"/>
</aura:application>

You can download the code from GitHub by using below URL:
Lightning-Treegrid-Generic-Component-to-display-hierarchy-in-tabular-format-for-any-sobject

Note:

  • Specify Field API properly as javascript is case sensitive. For example, specify "Name" instead of "name"
  • For adding parent field API names, provide API names properly. For example for Account owner use, "Owner.Name" instead of "owner.name".
Hope this will help!!!

8 comments:

  1. This is an awesome post. Really very informative and creative. This sharing concept is a good way to enhance the knowledge. Thank you very much for this post. I like this site very much. I like it and it help me to development very well...
    Mobile App Development Company In Chennai
    Android App Development Company In Chennai
    Android Application Development Company In Chennai
    Custom Web Application Development Company In Chennai

    ReplyDelete
  2. Hi, Is there any way we can select child rows on selecting parent row? For example, We have a requirement where once user select parent account, it should select all contacts specific to that account.

    ReplyDelete
    Replies
    1. Please ping if u got any solution regarding the same

      Delete
    2. Refer below link

      https://www.infallibletechie.com/2020/05/lightningtreegrid-select-child-rows.html

      Delete
  3. Great Blog... The information you shared is very effective for learners I have got some important suggestions from it, Keep Sharing such a nice blog.
    custom web application development company

    ReplyDelete
  4. Is it possible to NOT show toggle icon when there are no child records for a parent record in tree grid

    ReplyDelete
  5. Could you share this is an LWC format?

    ReplyDelete