Lightning · Salesforce

Dynamic Reusable Lightning Table Component

Don’t Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of software development patterns. Most of the time we will find creating same kind of Component for different object over and over again. To over come this we have created Dynamic reusable lightning component.

Github Link : https://github.com/gokulrajanoff/dynamic-table-

This component is for displaying the record in a table format, that allow sorting on header fields (without using any extra libraries) and the table is having fixed header and scrolling enabled. This component runs on standard as well as custom object.

To DO :

  1. Create Apex class :(Name It as you Like) -> Copy Paste entire Apex code
  2. Create Lightning Component -> Copy Paste entire component code , don’t forget to change the controller name to your apex , otherwise I am not responsible for the error.
  3. Controller will be automatically calculated, so no problem ,Copy Paste controller.js code , change the action,setparam() -> according to  your object and field to display.
  4. Preview as simple as that.

Overview of this Component:
Apex Class : Table Class
Method : Get_Record_For_Table_Input_From_Query(String objectName,String queryFields,String fullQuery)
Return : Object that can be displayed on Table
objectName = Name of the Object.
queryFields = Fields we want to display.
fullQuery = Full Soql Query.

Call this method and this will return data necessary for the table to be displayed.

Screen Shot 2018-04-25 at 7.10.29 PM

Code :
Apex Class :

public class DynamicTableClass {

    @AuraEnabled
    public static object Get_Record_For_Table_Input_From_Query(String objectName,String queryFields,String fullQuery)
    {
        Integer headerindex=0; 
        TargetTable TargetTable_inst = new TargetTable();
        List allListOfRecords = new List();
        String Query=fullQuery;
        //split all the fields into a list of strings
        List fieldList= queryFields.split(',');
        //Creating a Dynamic Object Type with Custom object name
        String listType = 'List';
        //Creating a Dynamic Record Type with Custom object name
        String EachRecordType =  ''+objectName+'';
        System.debug('listType'+listType);
        System.debug('EachRecordType'+EachRecordType);
        //Creating a generic Sobject to hold data of custom object
        List ListOfRecords = (List)Type.forName(listType).newInstance();
        SObject IndividualMember = (SObject)Type.forName(EachRecordType).newInstance();
        //Execute the Query to get and store the data in Generic object
        ListOfRecords = Database.query(Query);
        system.debug(ListOfRecords);
        //object field map
        Map objectFields = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap();
        for(Integer i=0;i<ListOfRecords.size();i++)
            {
                IndividualMember = ListOfRecords[i];
                EachRecord Each_record_nested_List_Of_Fields = new EachRecord();
                List temp = new List();
              for(String field :fieldList)
              {   //record id
                  if(field=='id')
                  {
                      Each_record_nested_List_Of_Fields.recordId=(Id)IndividualMember.get(field);
                  }
                  //if there is any relation like account.name
                  if(field.containsAny('\\.'))
                  {
                      List relation = field.split('\\.');
                      //get the actual value not id in relation
                      temp.add(IndividualMember.getSobject(relation[0]).get(relation[1]));
                  }
                  else
                  {		//except is store all field and values
                      if(field!='id'){
                     temp.add((IndividualMember.get(field)==null)?'':IndividualMember.get(field));
                      }
                  }
               }
                System.debug('i='+i+'-'+temp);
               Each_record_nested_List_Of_Fields.recordValue=temp;
			allListOfRecords.add(Each_record_nested_List_Of_Fields);
            }
        
        //prepare header
        List<header> headerList = new List<header>();
        
        for(String field :fieldList)
        {
            if(field!='id'){
            header h = new header();
            h.index=headerindex++;
            String s = field;
            //removing __c
            Integer i=s.lastIndexOf('_');
            if(i!=-1)
            {
               s=s.substring(0,i);
            }
            //replacing _ with space so we get account_name = account name
            s=s.replaceAll('_',' ');
            system.debug('String'+s);
            h.name=s;
            h.sorted=2;
            headerList.add(h);
            }
        }
        //system.debug('Header:'+headerList);
        TargetTable_inst.headerList=headerList;
        TargetTable_inst.records=ListOfRecords;
        TargetTable_inst.ListOfEachRecord=allListOfRecords;
        return TargetTable_inst;
        
    }
public class header{
        @AuraEnabled public String name; //Name of the Field
        @AuraEnabled public String api;  //Api of the Field
        @AuraEnabled public Integer sorted; //Is the FieldSorted
        @AuraEnabled public Integer index; //Index of the Field on record
    }
    public class EachRecord{
        @AuraEnabled public Map fieldValuePair; //The field and Its corresponding Value
        @AuraEnabled public List recordValue;  //Record
        @AuraEnabled public Id recordId; //Record Id used for Firing Events
    }
    public class TargetTable{
        @AuraEnabled public List<header> headerList; //Header wrapper
        @AuraEnabled public List  records; //actual list of records
        @AuraEnabled public List  ListOfEachRecord; //curated wrapped list of record facilitated for table 
    }
}

Lightning Component.cmp:

<aura:component controller="DynamicTableClass" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
	<aura:attribute name="Table_header_Records" type="object"/> 
    <aura:attribute name="Error" type="boolean" default="false" description="use for displaying error in query or backend exception"/>
    <aura:attribute name="Message" type="boolean" default="false" description="use for display no record found message"/>
    <aura:handler name="init" value="{!this}" action="{!c.getRecordsForTable}"/>
    <aura:attribute name="showHeaders" type="Boolean" default="false"/> 
    <article class="slds-card">
    <aura:if isTrue="{!v.Error}">
               <div class="slds-text-color--error">Error In Query Or Backend Exception, check With it </div>
            </aura:if>
        <aura:if isTrue="{!v.Message}">
               <div class="slds-text-color--error"> No Result Found...</div>
            </aura:if>
       <aura:if isTrue="{!v.showHeaders}">
           <div class="slds-center" style="align:center;">
       <div class="slds-table--header-fixed_container" style="height:350px;">
    <div class="slds-scrollable_y" style="height:100%;">
       <table class="slds-table slds-table_bordered slds-table--header-fixed">
         <thead>
           <tr class="slds-text-title--caps">
               <aura:iteration items="{!v.Table_header_Records.headerList}" var="h"> 
               <th scope="col" onclick="{!c.sortColumn}" data-value="{!h.index}">
                  <div class="slds-truncate slds-cell-fixed" title="{!h.name}">{!h.name}
            <aura:if isTrue="{! (h.sorted == 0) }">&nbsp;  ▼ </aura:if>  
            <aura:if isTrue="{! (h.sorted == 1) }"> &nbsp;  ▲ </aura:if>
                   </div>
               </th>
               </aura:iteration>
               </tr>
         </thead>
         <tbody>
            <aura:iteration items="{!v.Table_header_Records.ListOfEachRecord}" var="tar">
                    <tr onclick="{!c.displayfields}" data-value="{!tar.recordId}">
                  <aura:iteration items="{!tar.recordValue}" var="value">
                  <td>
                    <a><div class="slds-truncate">{!value}</div></a> 
                  </td>
                  </aura:iteration>  
               </tr>
            </aura:iteration>
         </tbody>
      </table>
       </div></div>
        </div>
           </aura:if> 
    </article>
</aura:component>

Lightning Controller.js:

({
	getRecordsForTable : function(component, event, helper) {
        var action=component.get("c.displayTable");
        action.setCallback(this,function(response)
                           {
                               component.set("v.Table_header_Records",response.getReturnValue());
                               var records = component.get("v.Table_header_Records");
                               console.log(component.get("v.Table_header_Records"));
                               if(records==null)
                               {
                                   component.set("v.Error", true);
                                   component.set("v.showHeaders",false);
                               }
                               if (records!=null && records.ListOfEachRecord.length == 0) {
                                   component.set("v.Message", true);
                                   component.set("v.showHeaders",false);
              
                               } else {
                                   component.set("v.Message", false);
                                   component.set("v.showHeaders",true);
                               }
                           });
        $A.enqueueAction(action);
    },
    sortColumn : function(component, event, helper) {
        var ctarget = event.currentTarget;
        var Fieldindex = ctarget.dataset.value;
        console.log("selected Field" Fieldindex);
        var Wrapedrecords = component.get("v.Table_header_Records");
        var objectRecord = Wrapedrecords.ListOfEachRecord;
        var headerRecord = Wrapedrecords.headerList;
        console.log(headerRecord);
        for(var i=0;i<headerRecord.length;i  ){
            console.log("loop:" headerRecord[i].name " - " Fieldindex);
            if(headerRecord[i].index==Fieldindex)
            {	console.log("match entered");
             var sorted = headerRecord[i].sorted;
             if(sorted==1)
             {
                 //desc
                 console.log("descending");
                 objectRecord.sort(function(a,b) {
                     return (a['recordValue'][headerRecord[i].index].toString().toLowerCase()  < b['recordValue'][headerRecord[i].index].toString().toLowerCase() ) ? 1 : ((b['recordValue'][headerRecord[i].index].toString().toLowerCase()  < a['recordValue'][headerRecord[i].index].toString().toLowerCase() ) ? -1 : 0);} );
                 sorted=0;
             }
             else if(sorted==0 || sorted==2)
             {
                 console.log("ascending");
                 objectRecord.sort(function(a,b) {return (a['recordValue'][headerRecord[i].index].toString().toLowerCase()  > b['recordValue'][headerRecord[i].index].toString().toLowerCase() ) ? 1 : ((b['recordValue'][headerRecord[i].index].toString().toLowerCase()  > a['recordValue'][headerRecord[i].index].toString().toLowerCase() ) ? -1 : 0);} ); 
                 sorted=1;
             }
             headerRecord[i].sorted=sorted; 
             
            }
            else{
                headerRecord[i].sorted=2;
            }
        }
        Wrapedrecords.records=objectRecord;
        Wrapedrecords.headerList=headerRecord;
        console.log(Wrapedrecords);
        component.set("v.Table_header_Records",Wrapedrecords);
        
        
    }
    
})

Application :

   <aura:application extends="force:slds">
	<center><h1>Table1</h1></center>
    <c:testDynamicComponent ></c:testDynamicComponent>
    <br/>
    <center><h1>Table2</h1></center>
    <c:DynamicTablecomponent ></c:DynamicTablecomponent>
</aura:application>

Output:
Screen Shot 2018-04-25 at 6.54.28 PM

Clicking on the Column will sort the column:
Screen Shot 2018-04-25 at 6.55.48 PM

Note:
Place we need to modify code to make it run on different object

var action=component.get("c.Get_Record_For_Table_Input_From_Query");
        action.setParams({"objectName":"contact","queryFields":"name,title,phone,email","fullQuery":"select name,title,phone,email from contact"});

Or we can call this method DynamicTableClass.Get_Record_For_Table_Input_From_Query(objectName, queryFields, fullQuery) from other apex class.

Similarly we can create Different component that will display records from different object by just modifying the parameter passed to Get_Record_For_Table_Input_From_Query() function.

Screen Shot 2018-04-25 at 7.00.30 PM

Other Features:

  1. On Click of a row, a Controller function will be called , we can get the record id of that row by
var ctarget = event.currentTarget;
var RecordID = ctarget.dataset.value;

we can use the Record Id to fire events or else do something as you wish :D.

2. Sorting Done Client side without extra libraries , so no headache in creating static resource fields. And its fast , no server calls.

3.Scrollable View , so we can see may records at a time , we can adjust the height of table according to your wish.

3 thoughts on “Dynamic Reusable Lightning Table Component

  1. I am working on using this ( this is so good for many things). In your git comments you note to just go to DynamicTableapp.app, and change the query. I can’t find this, can you give some more direction on this. Do i have to still copy all the code? ( in a sandbox and then deploy?) change that code then it will work?

    Like

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 )

Twitter picture

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

Facebook photo

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

Connecting to %s