Lightning Component – Display records in a dynamic table using FieldSet on any object

Recently, while answering on developer forums. I came across a question where someone asked about a lightning component to display records in the table using a fieldset. I tried to put my effort to create a generic lightning component which can be used with any object and a fieldset.

This component can be used to display child records or all the records of an object. See below component code.

<c:LightningTable sObjectName="Opportunity" fieldSetName="lightningapps__opportunitytable" parentFieldName="AccountId" parentRecordId="00190000016cYL6AAM"/>

As you can see in the above component we have 4 attributes. These attributes can control the behaviour of the component. sObjectName : Name of the object which you want to query records from. (Required) fieldSetName: Name of the fieldset which will be used for the columns in the table. (Required) parentFieldName: If you want to display only child records of a parent then this should be specified. (Optional) parentRecordId: Parent record Id if you are displaying child records of a parent. (Optional) LightningTable is a parent component which is working as a table wrapper. This parent component is using a LightningCell component to display each cell of the row based on the data type. You can add more data types into the code as per your requirement. I am providing you all the code related to these components so you can update it as per your business use case. Fieldset is used to add/remove columns dynamically to the table.

LightningTable component

<aura:component controller="LightningTableController" implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >

    <aura:attribute name="sObjectName" type="String"/>
    <aura:attribute name="fieldSetName" type="String"/>
    <aura:attribute name="fieldSetValues" type="List"/>
    <aura:attribute name="tableRecords" type="List"/>
    <aura:attribute name="parentFieldName" type="String"/>
    <aura:attribute name="parentRecordId" type="String"/>

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<div class="slds">
<table class="slds-table slds-table--bordered">
<thead>
<tr>
                	<aura:iteration items="{!v.fieldSetValues}" var="field">
<th> {!field.label}</th>
</aura:iteration></tr>
</thead>
<tbody>
                <aura:iteration items="{!v.tableRecords}" var="row">
<tr>
                        <aura:iteration items="{!v.fieldSetValues}" var="field">
<td> <c:LightningCell record="{!row}" field="{!field}"/></td>
</aura:iteration></tr>
</aura:iteration></tbody>
</table>
</div>
</aura:component>

LightningTableController [APEX Class]

public class LightningTableController {

    @AuraEnabled
	public static String getFieldSet(String sObjectName, String fieldSetName) {
        String result = '';
        try{
            SObjectType objToken = Schema.getGlobalDescribe().get(sObjectName);
            Schema.DescribeSObjectResult d = objToken.getDescribe();
            Map<String, Schema.FieldSet> FsMap = d.fieldSets.getMap();
            system.debug('>>>>>>> FsMap >>> ' + FsMap);
            if(FsMap.containsKey(fieldSetName))
                for(Schema.FieldSetMember f : FsMap.get(fieldSetName).getFields()) {
                    if(result != ''){
                        result += ',';
                    }
                    String jsonPart = '{';
                    jsonPart += '"label":"' + f.getLabel() + '",';
                    jsonPart += '"required":"' + (f.getDBRequired() || f.getRequired()) + '",';
                    jsonPart += '"type":"' + (f.getType()) + '",';
                    jsonPart += '"name":"' + f.getFieldPath() + '"';
                    jsonPart += '}';
                    result += jsonPart;
            }
        }
        catch(Exception e){
            result += e.getLineNumber() + ' : ' + e.getMessage();
        }
        return '['+result+']';
    }

    @AuraEnabled
    public static String getRecords(String sObjectName, String parentFieldName, String parentRecordId, String fieldNameJson){

        List<sObject> lstResult = new List<sObject>();
        String result = '[]';
        try{
            List<String> fieldNames = (List<String>) JSON.deserialize(fieldNameJson, List<String>.class);
            Set<String> setFieldNames = new Set<String>();
            String query = 'SELECT ' + String.join(fieldNames, ',') + ' FROM ' + sObjectName;
            if(parentFieldName != NULL && parentFieldName != '' && parentRecordId != NULL){
                query += ' WHERE ' + parentFieldName + '= \'' +  parentRecordId + '\'';
            }
            for(sObject s : Database.query(query)){
                lstResult.add(s);
            }
            if(lstResult.size() > 0) {
                result = JSON.serialize(lstResult);
            }
        }
        catch(Exception e){
            result += e.getLineNumber() + ' : ' + e.getMessage();
        }
        return result;
    }
}

LightningTableController.js

({
	doInit : function(component, event, helper) {
		helper.doInit(component, event, helper);
	}
})

LightningTableHelper.js

({
	doInit : function(component, event, helper) {
    	helper.getTableFieldSet(component, event, helper);
	},

    getTableFieldSet : function(component, event, helper) {
        var action = component.get("c.getFieldSet");
        action.setParams({
            sObjectName: component.get("v.sObjectName"),
            fieldSetName: component.get("v.fieldSetName")
        });

        action.setCallback(this, function(response) {
            var fieldSetObj = JSON.parse(response.getReturnValue());
            component.set("v.fieldSetValues", fieldSetObj);
            //Call helper method to fetch the records
            helper.getTableRows(component, event, helper);
        })
        $A.enqueueAction(action);
    },

    getTableRows : function(component, event, helper){
        var action = component.get("c.getRecords");
        var fieldSetValues = component.get("v.fieldSetValues");
        var setfieldNames = new Set();
        for(var c=0, clang=fieldSetValues.length; c<clang; c++){             if(!setfieldNames.has(fieldSetValues[c].name)) {                 setfieldNames.add(fieldSetValues[c].name);                   if(fieldSetValues[c].type == 'REFERENCE') {                     if(fieldSetValues[c].name.indexOf('__c') == -1) {                     	setfieldNames.add(fieldSetValues[c].name.substring(0, fieldSetValues[c].name.indexOf('Id')) + '.Name');                          }                     else {                     	setfieldNames.add(fieldSetValues[c].name.substring(0, fieldSetValues[c].name.indexOf('__c')) + '__r.Name');                              }                 }             }         }         var arrfieldNames = [];         setfieldNames.forEach(v => arrfieldNames.push(v));
        console.log(arrfieldNames);
        action.setParams({
            sObjectName: component.get("v.sObjectName"),
            parentFieldName: component.get("v.parentFieldName"),
            parentRecordId: component.get("v.parentRecordId"),
            fieldNameJson: JSON.stringify(arrfieldNames)
        });
        action.setCallback(this, function(response) {
            var list = JSON.parse(response.getReturnValue());
            console.log(list);
            component.set("v.tableRecords", list);
        })
        $A.enqueueAction(action);
    },

    createTableRows : function(component, event, helper){

	}
})

I have created one more component to use fieldset while displaying columns. As you know, we can not reference fields dynamically in the component so I used Javascript to get the value and update the view.

LightningCell Component

<aura:component access="global">
    <aura:attribute name="record" type="sObject" description="record which is being displayed"/>
    <aura:attribute name="field" type="Object" description="field object which is being rendered"/>
    <aura:attribute name="cellValue" type="Object"/>
    <aura:attribute name="cellLabel" type="String"/>
    <aura:attribute name="isTextField" type="boolean" default="false"/>
    <aura:attribute name="isReferenceField" type="boolean" default="false"/>
    <aura:attribute name="isDateField" type="boolean" default="false"/>
    <aura:attribute name="isDateTimeField" type="boolean" default="false"/>
    <aura:attribute name="isCurrencyField" type="boolean" default="false"/>

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:if isTrue="{!v.isTextField}">
    	<ui:outputText value="{!v.cellValue}"/>
    </aura:if>
    <aura:if isTrue="{!v.isDateField}">
    	<ui:outputDate value="{!v.cellValue}"/>
    </aura:if>
    <aura:if isTrue="{!v.isDateTimeField}">
    	<ui:outputDateTime value="{!v.cellValue}"/>
    </aura:if>
    <aura:if isTrue="{!v.isCurrencyField}">
    	<ui:outputCurrency value="{!v.cellValue}"/>
    </aura:if>
    <aura:if isTrue="{!v.isReferenceField}">
    	<ui:outputURL value="{!'/one/one.app?#/sObject/'+ v.cellValue + '/view'}" target="_blank" label="{!v.cellLabel}"/>
    </aura:if>
</aura:component>

LightningCellController.js

({
	doInit : function(component, event, helper) {
		helper.doInit(component, event, helper);
	}
})

LightningCellHelper.js

({
	doInit : function(component, event, helper) {
		var record = component.get("v.record");
        var field = component.get("v.field");

        component.set("v.cellValue", record[field.name]);
        if(field.type == 'STRING' || field.type == 'PICKLIST')
            component.set("v.isTextField", true);
        else if(field.type == 'DATE'){
        	component.set("v.isDateField", true);
        }
        else if(field.type == 'DATETIME'){
        	component.set("v.isDateTimeField", true);
        }
        else if(field.type == 'CURRENCY'){
        	component.set("v.isCurrencyField", true);
        }
        else if(field.type == 'REFERENCE'){
        	component.set("v.isReferenceField", true);
            var relationShipName = '';
            if(field.name.indexOf('__c') == -1) {
                relationShipName = field.name.substring(0, field.name.indexOf('Id'));
            }
            else {
                relationShipName = field.name.substring(0, field.name.indexOf('__c')) + '__r';
            }
            component.set("v.cellLabel", record[relationShipName].Name);
        }
	}
})

Reference/lookup fields are handled explicitly in the above code as they can be included as a field in any fieldset. I tried to make it as much as dynamic.

PREVIEW

20170926-171657_capture

One thought on “Lightning Component – Display records in a dynamic table using FieldSet on any object

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s