Skip to main content

Enhanced Dynamic DataTable using LWC to dynamically select columns (Fields) to display for any object

How we can show dynamic DataTable with Lightning Web Component ?

Lightning Web Components

In this post we will see how we can create a dynamic datatable. We will see how we can enhance custom Datatable to dynamically generate URL and display the clickable link in Lightning Datable for Formual Field with Hyperlink functions or VisualforcePage Link o a field, Reference field(Look-up, Master Detail or Standard reference field Created By, Modified By). Basically we will see enhanced custom Datatable to choose the columns similar to standard "Select field to display" on Object tab.


Scenario:
A developer has created a dynamic Datatable for the Business and he left teh organization. Now after few days Admin has added a new Formual Field on an object which is using Hyperlink function. Now this new column in the Table should be added along with the clickable URL.

End user can select the fields to add in the existing custom datatable similar to Standard select fields to display.

Steps to implement
Step 1: Create an Object DataTableConfigurationObject__c
    This object is created to store the fields selected by the user . Everytime user updates the columns or selects field this object will be updated.
Step 2: Create two custom fields in this object to store the ObjectAPI and FieldAPIName.


* I have used Custom Object instead of custom settings because the character limit was exceeding for Fields API name and Custom metadata needs to be deployed every time a user updates a field. If you do not want to give end user access to control the columns we can simply use flows to pass the predefined columns and Object name.

Step 3: Create dynamicDataTableController Apex Class. This Class is used to query teh field name , API name and do other upsert operations.
 
Apex Class
dynamicDataTableController.cls
public with sharing class dynamicDataTableController {

   @AuraEnabled
   public static String updateSelectedFields(String objectName, String fieldAPINames){
      String getNameFieldAPI = '';
      try{
         for(String str : fieldAPINames.split(',')){
            if(str.containsIgnoreCase('.Name')){
               if(str.containsIgnoreCase('__c.Name')){
                  getNameFieldAPI += str.Substring(0,str.length()-5) + ',' ;
               }else{
                  getNameFieldAPI += str.Substring(0,str.length()-5) + 'Id'+',' ;
               }
            }else{
               getNameFieldAPI += str+ ',' ;
            }
         }
         List<DataTableConfigurationObject__c> fieldAPINameStored = new List<DataTableConfigurationObject__c>();
         List<DataTableConfigurationObject__c> updatetableConfig = new List<DataTableConfigurationObject__c>();
         fieldAPINameStored = [SELECT Id,Name,ObjectAPIName__c,All_Field_API_Name__c FROM DataTableConfigurationObject__c WHERE ObjectAPIName__c =:objectName LIMIT 1];
         if(fieldAPINameStored.size() > 0){
            for(DataTableConfigurationObject__c dataconfg : fieldAPINameStored){
               DataTableConfigurationObject__c objDataTableConfig = new DataTableConfigurationObject__c();
               objDataTableConfig.All_Field_API_Name__c = getNameFieldAPI.removeEnd(',');
               objDataTableConfig.Id = dataconfg.Id ;
               objDataTableConfig.ObjectAPIName__c = objectName;
               updatetableConfig.add(objDataTableConfig);
            }        
         }else{
            DataTableConfigurationObject__c objDataTableConfig = new DataTableConfigurationObject__c();
            objDataTableConfig.All_Field_API_Name__c =  getNameFieldAPI.removeEnd(',');
            objDataTableConfig.ObjectAPIName__c = objectName;
            updatetableConfig.add(objDataTableConfig);
         }
         if(updatetableConfig.size() > 0){
            upsert updatetableConfig;
         }
         return 'Success';
      }catch(Exception ex){
         throw new AuraHandledException(ex.getMessage());
      }
   }

   @AuraEnabled
   public static List<SObject> retreieveRecords(String objectName, String fieldAPINames){
      String fieldApi = '';
      String getNameFieldAPI = '';
      for(String str : fieldAPINames.split(',')){
         if(str.containsIgnoreCase('__c.Name')){
            fieldApi = fieldApi + str.Substring(0,str.length()-5)+',';
         }else{
            fieldApi = fieldApi + str+ ',' ;
         }
      }
      fieldApi = fieldApi.removeEnd(',');
      String strQuery = 'SELECT ' + fieldApi + ' FROM ' + objectName ; //+ ' LIMIT 10' ;
      System.debug('database.query(strQuery) $$ '+database.query(strQuery) );  
      return database.query(strQuery);
   }

   //Retrieve field details based on Object API Name
   @AuraEnabled (cacheable=true)
   public static String retreieveFields(String ObjectName){
   
      MapFieldWrapperClass fieldMapWrap = new MapFieldWrapperClass();
      Map<String, String> existingmapField = new Map<String, String>();
      Map<String, String> newmapField = new Map<String, String>();
      Map<String, String> allmapField = new Map<String, String>();      
     
      Set<String> setFieldList  = new  Set<String>();
      List<String> fieldAPIList = new  List<String>();
      System.debug('ObjectName =>'+ObjectName );
      String RegEx ='<\\/?[^>]*>';
      if(!String.isEmpty(ObjectName)){
         List<DataTableConfigurationObject__c> fieldAPINameStored = [SELECT Id,Name,ObjectAPIName__c,All_Field_API_Name__c FROM DataTableConfigurationObject__c WHERE ObjectAPIName__c =:ObjectName];
         if(fieldAPINameStored.size() > 0 && fieldAPINameStored[0].All_Field_API_Name__c != null ){
            System.debug('existing data  fieldAPINameStored[0].All_Field_API_Name__c =>' + fieldAPINameStored[0].All_Field_API_Name__c);
            String unescapeString = fieldAPINameStored[0].All_Field_API_Name__c.replaceAll(RegEx, '');
            System.debug('Unescaped String: => '+unescapeString);
            for(String str : unescapeString.split(',')){
               fieldAPIList.add(str);
            }
            existingmapField = sobjectListField(fieldAPIList,ObjectName);
            System.debug('existing existingmapField $$ '+existingmapField);
            fieldMapWrap.selectedFieldsMap = sortFieldMapInAscending(existingmapField);
            System.debug('fieldMapWrap.selectedFieldsMap => '+fieldMapWrap.selectedFieldsMap );
         }
         System.debug('New data else block ' );
         SObjectType sObjType = ((SObject) Type.forName(ObjectName).newInstance()).getSObjectType();
         for(Schema.SObjectField fld: sObjType.getDescribe().fields.getMap().values()){
            if(fld.getDescribe().getType() == Schema.DisplayType.REFERENCE){
               allmapField.put(fld.getDescribe().getLabel().replaceAll('ID',''),fld.getDescribe().getRelationshipName() + '.Name');
            }else if(fld.getDescribe().getCalculatedFormula() != null && fld.getDescribe().getCalculatedFormula().containsIgnoreCase('hyperlink')){
               allmapField.put(fld.getDescribe().getLabel(),fld.getDescribe().getName() + '.Name');
            }else if(fld.getDescribe().getName() != 'Id'){
               allmapField.put(fld.getDescribe().getLabel(),fld.getDescribe().getName());
            }
         }
         fieldMapWrap.allAvailableFieldsMap = sortFieldMapInAscending(allmapField);
         System.debug('fieldMapWrap.allAvailableFieldsMap => '+fieldMapWrap.allAvailableFieldsMap );
      }
      return JSON.serialize(fieldMapWrap);
   }

   public static  Map<String, String> sortFieldMapInAscending(Map<String, String> mapField){
      Map<String, String> sortedMap = new Map<String, String>();
      List<String> fieldlabelList = new List<String>();
      fieldlabelList.addAll(mapField.keySet());  
      fieldlabelList.sort();
      for (Integer i = (fieldlabelList.size() - 1); i >= 0; i--){
         sortedMap.put(fieldlabelList[i], mapField.get(fieldlabelList[i]));
         // System.debug('fieldlabelList ' + i + ' $$' + fieldlabelList[i] + ' == '+ mapField.get(fieldlabelList[i]));
      }
      System.debug('new sortedMap =>' + sortedMap);
      return sortedMap;
   }

   public static Map<String, String> sobjectListField(List<String> fieldList,String ObjectName){
      System.debug('fieldList $$ '+fieldList);
      System.debug('ObjectName $$ '+ObjectName);
      Schema.DescribeSObjectResult objDescribe = Schema.getGlobalDescribe().get(ObjectName).getDescribe();
      Map<String, String> newmapField = new Map<String, String>();
      for(String sfld: fieldList){
         System.debug('Schema.SObjectField Label $$ '+ objDescribe.fields.getMap().get(sfld).getDescribe().getLabel());
         System.debug('sfld $$ '+sfld);
         Schema.DescribeFieldResult fieldResult = objDescribe.fields.getMap().get(sfld).getDescribe();
         if(fieldResult.getType() == Schema.DisplayType.REFERENCE){
            newmapField.put(fieldResult.getLabel().replaceAll('ID',''),fieldResult.getRelationshipName() + '.Name');
         }else if(fieldResult.getCalculatedFormula() != null && fieldResult.getCalculatedFormula().containsIgnoreCase('hyperlink')){
            newmapField.put(fieldResult.getLabel(),fieldResult.getName() + '.Name');
         }else if(fieldResult.getName() != 'Id'){
            newmapField.put(fieldResult.getLabel(),fieldResult.getName());
         }
      }
      return newmapField;
   }

   public class MapFieldWrapperClass {
      @AuraEnabled
      public Map<String, String> selectedFieldsMap;
      @AuraEnabled
      public Map<String, String> allAvailableFieldsMap;
   }
}

Step 4: Create childDatable. This component holds the button "Select Fields to display" which will pop up the dual box to select the fields to be diaplyed.

childDatatable.html
<template>

    <!-- Button to open the dual Box for selecting fields -->
    <lightning-button
            variant="brand"
            label="Select Fields to display"
            title="Select Fields to display"
            onclick={handleSelectFields}
            class="slds-m-left_x-small">
    </lightning-button>

    <template if:true={isModalOpen}>

        <!-- Modal/Popup Box LWC starts here -->
        <section    role="dialog" tabindex="-1"
                    aria-labelledby="modal-heading-01" aria-modal="true"
                    aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
            <div class="slds-modal__container">

                <!-- Modal/Popup Box LWC header here -->
                <header class="slds-modal__header">
                    <button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse"
                            title="Close" onclick={closeModal}>
                        <lightning-icon icon-name="utility:close" alternative-text="close" variant="inverse" size="small"></lightning-icon>
                        <span class="slds-assistive-text">Close</span>
                    </button>
                    <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">Select Fields to display</h2>
                </header>


                <!-- Modal/Popup Box LWC body starts here -->
                <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                    <template if:true={errorStatus}>
                        <div class="slds-box slds-box_small slds-theme_error">
                            <p>Review the errors on this page.</p>
                        </div>
                        <div class="slds-m-left_large">
                            <p style="color: red;">
                                Select at least one field to display.
                            </p>
                        </div>
                    </template>
                    <div class="slds-m-left_medium">
                        <lightning-dual-listbox name="languages"
                                                label="Select Fields"
                                                source-label="Available Fields"
                                                selected-label="Visible Fields"
                                                field-level-help="Select the fields to display"
                                                options={statusOptions}
                                                value ={selectedValues}
                                                onchange={handleChange}></lightning-dual-listbox>
                    </div>
                </div>


                <!-- Modal/Popup Box LWC footer starts here -->
                <footer class="slds-modal__footer">
                    <button class="slds-button slds-button_neutral" onclick={closeModal} title="Cancel">Cancel</button>
                    <button class="slds-button slds-button_brand" onclick={handleSaveButtonClick} title="Save">Save</button>
                </footer>
            </div>
        </section>
       
        <div class="slds-backdrop slds-backdrop_open"></div>
    </template>
    <br />
</template>

childDatatable.js
import { LightningElement, api } from 'lwc';
import retreieveFields from '@salesforce/apex/dynamicDataTableController.retreieveFields';
import updateFields from '@salesforce/apex/dynamicDataTableController.updateSelectedFields';
export default class ChildDatatable extends LightningElement {

  @api fecthObjectName;  // holds object Name passed from parent component
  fieldItems = [];  // all fields map of field API and field Label
  _selected = [];   // all fields selected
  selectedfieldItems = [];
  isModalOpen = false;
  isError = false;
  selectedfieldMap ;
  isRendered;
  fieldAPINames;


  connectedCallback() {
    // function to fetch all fields map field API and field Label
    retreieveFields({
      ObjectName: this.fecthObjectName
    })
      .then(data => {
        console.log('wrapper data =>' + data);
        let result = JSON.parse(data);
        this.selectedfieldMap = result.selectedFieldsMap;
        let fieldAPILabelMap = result.allAvailableFieldsMap;
        console.log(' wrapper data.selectedfieldMap => ' + JSON.stringify( this.selectedfieldMap));
        console.log(' wrapper result.fieldAPILabelMap => ' + JSON.stringify(fieldAPILabelMap));
        for (var key in fieldAPILabelMap) {
          if (fieldAPILabelMap.hasOwnProperty(key)) {
            this.fieldItems.push({ value: fieldAPILabelMap[key], label: key });
          }
        }
        if(this.selectedfieldMap){
          for (var key in this.selectedfieldMap) {
            if (this.selectedfieldMap.hasOwnProperty(key)) {
              this.selectedfieldItems.push(this.selectedfieldMap[key]);
            }
          }
          this._selected = [... this.selectedfieldItems];
          this.handleRetreiveRecords();
        }
        this.error = undefined;
      }).catch(error => {
        this.error = error;
        this.fieldItems = undefined;
      })
  }

  handleSelectFields() {
    this.isModalOpen = true;
    //this._selected = [];
  }

  closeModal() {
    this.isModalOpen = false;
    this.isError = false;
  }

  get statusOptions() {
    console.log(' this.fieldItems => ' + JSON.stringify(this.fieldItems));
    return this.fieldItems;
  }

  get selectedValues() {
    return this._selected;
  }

  get errorStatus() {
    return this.isError;
  }

  handleChange(e) {
    this._selected = e.detail.value;
    console.log(' this._selected => ' + this._selected);
    if (this._selected.length > 0) {
      this.isError = false;
    }
  }

  handleSaveButtonClick(){
    if (this._selected.length > 0) {
      this.updateSelectedFields();
      this.handleRetreiveRecords();
    }else{
      this.isError = true;
    }
  }

  updateSelectedFields() {
    console.log('updateSelectedFields => ' );
    let selectedFieldsValue = this._selected;
    this.fieldAPINames = selectedFieldsValue.toString();
    console.log('updateSelectedFields this.fecthObjectName => ' +this.fecthObjectName);
    console.log('updateSelectedFields this.fieldAPINames => '+this.fieldAPINames);
    updateFields({
      objectName: this.fecthObjectName,
      fieldAPINames: this.fieldAPINames})
      .then(results => {
        console.log('results => ' + results);
      })
      .catch(error => {
        console.log('error => ' + JSON.stringify(error));
      });
  }

  handleRetreiveRecords() {
    let selectedFieldsValue = this._selected;
    const fieldAPIMapSelected = [];

    if (this._selected.length > 0) {

      this.isModalOpen = false;
      this.isError = false;

      selectedFieldsValue.forEach(option => {
        let currentOption = this.fieldItems.find(obj => obj.value === option);
        fieldAPIMapSelected.push({
          label: currentOption.label,
          value: currentOption.value
        });
      });
      console.log('selectedFieldsValue => ' + selectedFieldsValue);

      const fieldAPIMapSelected2 = fieldAPIMapSelected;
      console.log(' this.fieldAPIMapSelected2 => ' + JSON.stringify(fieldAPIMapSelected2));
      const evtCustomEvent = new CustomEvent(
        'retreive',
        {
          detail: { selectedFieldsValue, fieldAPIMapSelected2 }
        });
      this.dispatchEvent(evtCustomEvent);
      //this._selected = [];
    }
    else {
      this.isError = true;
    }
  }
}

childDatatable.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>51.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>

Step 5: Create genericDataTableLWC . This component is a dynamic datatable where we will pass the fields and columns to be displayed with pagination.

genericDataTableLWC.html
<template>
    <template if:true={recordsInPage}>
        <div class="dataTable" style="overflow: hidden;height:100%">

            <div style="overflow: hidden">
                <lightning-datatable
                    key-field="Id"
                    data={recordsInPage}
                    columns={columns}
                    sorted-by={sortBy}
                    sorted-direction={sortedDirection}
                    onsort={handleSortdata}
                    hide-checkbox-column={hideCheckBox}
                    errors={errors}>
                </lightning-datatable>
            </div>
            <template if:true={showPagination}>
                <div>
                    <lightning-layout multiple-rows class="slds-var-p-vertical_x-small slds-var-p-horizontal_small">
                        <lightning-layout-item size="4">
                            <div class="slds-float_left">
                                {pageNumberInfo}
                            </div>
                        </lightning-layout-item>
                        <lightning-layout-item size="4">
                            <div class="slds-align_absolute-center">
                                <a onclick={showFirstPage}>
                                    <lightning-button-icon icon-name="utility:left" size="small"
                                        alternative-text="First">
                                    </lightning-button-icon>
                                </a>
                                <a onclick={showPreviousPage}>
                                    <lightning-button-icon icon-name="utility:chevronleft" size="small"
                                        alternative-text="Previous">
                                    </lightning-button-icon>
                                </a>
                                <a onclick={showNextPage}>
                                    <lightning-button-icon icon-name="utility:chevronright" size="small"
                                        alternative-text="Next">
                                    </lightning-button-icon>
                                </a>
                                <a onclick={showLastPage}>
                                    <lightning-button-icon icon-name="utility:right" size="small"
                                        alternative-text="Next">
                                    </lightning-button-icon>
                                </a>
                            </div>
                        </lightning-layout-item>
                        <lightning-layout-item size="4">
                            <div class="slds-float_right">
                                <span>
                                    {recordsInfo}
                                </span>
                            </div>
                        </lightning-layout-item>
                    </lightning-layout>
                </div>
            </template>
        </div>
    </template>
    <template if:false={recordsInPage}>
        <h4> No Record Found for given criteria.</h4>
    </template>
</template>

genericDataTableLWC.js
import { LightningElement, api, track } from 'lwc';

export default class GenericDataTableLWC extends LightningElement {

    @api records = [];
    @api sortable = false;
    @track _selectedRows = [];
    @api showPagination = false;
    @api paginationSize = 10;
    @api hideCheckBox = false;
    @api showRowNumber = false;

    @track _pageSize = 10;
    @track sortedDirection = 'asc';

    @track draftValues = [];
    _startFromIndex = 0;
    _paginationInfo = {
        currentPage: 0,
        totalPages: 0
    };
    pageSize;
    @track _columns = [];
    @track recordSelected = false;
    @track _hideCheckBox = false;
    @api _records = [];
    @track recordsInPage = [];
    @track byPass = false;

    connectedCallback() {
        this.doinit();
        this.showFirstPage();
    }

    doinit = () => {
        if (this.paginationSize == undefined || this.paginationSize !== 10) {
            this.pageSize = this.paginationSize;
        }
        this._paginationInfo.totalPages = (((this.records.length / this.pageSize) - ((this.records.length % this.pageSize) / this.pageSize)) + (((this.records.length % this.pageSize) === 0) ? 0 : 1));
        this.fetchRecordFromRecords();
    }

    fetchRecordFromRecords = () => {
        if (this.records != undefined && this.columns != undefined) {
            this.recordsInPage = this.records;
            this._records = this.records;
        }

    }

    // invoked when column is changed
    @api
    get columns() {
        return this._columns;
    }
    set columns(value) {
        this._columns = value;
    }

    // invoked when selectedRows is changed
    @api
    get selectedRows() {
        return this._selectedRows;
    }
    set selectedRows(value) {
        if (this.byPass == false) {
            this._selectedRows = value;
        }
    }
    // invoked when page size is changed
    @api
    get pageSize() {
        if (!this.isNotBlank(this._pageSize)) this._pageSize = 10;
        return parseInt(this._pageSize, 10);
    }
    /**
     * @param {number} value
     */
    set pageSize(value) {
        this._pageSize = value;
    }

    isNotBlank = (checkString) => {
        return (checkString !== '' && checkString !== null && checkString !== undefined);
    }

    get pageNumberInfo() {
        if (this._records && this._records.length > 0) {
            this._paginationInfo.currentPage = (((this._startFromIndex + 1) / this.pageSize) - (((this._startFromIndex + 1) % this.pageSize) / this.pageSize) + ((((this._startFromIndex + 1) % this.pageSize) === 0) ? 0 : 1));
            return 'Page ' + this._paginationInfo.currentPage + ' of ' + this._paginationInfo.totalPages;
        }
        return 'Page 0 of 0';
    }
    //PAGINATION - INVOKED WHEN PAGE SIZE IS CHANGED

    paginationRefresh = () => {
        this._startFromIndex = 0;
    }

    //PAGINATION - SHOW First PAGE
    showFirstPage = () => {
        this.paginationRefresh();
        this.processPagination();
    }
    //PAGINATION - SHOW PREVIOUS PAGE
    showPreviousPage() {
        if (this._startFromIndex > 0) {
            this._startFromIndex = this._startFromIndex - this.pageSize;
            this.processPagination();
        }
    }

    //PAGINATION - SHOW NEXT PAGE
    showNextPage() {
        if (this._startFromIndex + this.pageSize < this._records.length) {
            this._startFromIndex = this._startFromIndex + this.pageSize;
            this.processPagination();
        }
    }

    //PAGINATION - SHOW LAST PAGE
    showLastPage = () => {
        let result = this._records.length % this.pageSize;
        if (this._startFromIndex >= 0) {
            if (result === 0) {
                this._startFromIndex = this._records.length - this.pageSize;
                this.processPagination();
            } else {
                this._startFromIndex = this._records.length - result;
                this.processPagination(true, -result);
            }
        }
    }


    // paginate the records
    processPagination(lastSetOfRecords = null, lastNumberOfRecords = null) {
        if (lastSetOfRecords) {
            this.recordsInPage = this._records.slice(lastNumberOfRecords);
        } else {
            this.recordsInPage = this._records.slice(this._startFromIndex, this.pageSize + this._startFromIndex);
        }
    }

    @api updateColumns(columns) {
        this.columns = [...columns];
    }

    @api refreshTable(result) {
        if (result.length === 0) {
            this.records = result;
        }
        this.refresh();
        if (result.length === 0) {
            console.log('Page length 0');
            this._paginationInfo = {
                currentPage: 0,
                totalPages: 0
            };
        }
    }


    refresh() {
        this.doinit();
        this.showFirstPage();
    }


    @api getSelected() {
        var el = this.template.querySelector('lightning-datatable');
        return el.getSelectedRows();
    }

    //Pagination Information in datatable footer
    get recordsInfo() {
        if (this._records.length > 0) {
            this._endIndex = this._startFromIndex + this.pageSize;
            return 'Showing ' + (this._startFromIndex + 1) + " to " + ((this._endIndex > this._records.length) ? this._records.length : this._endIndex) + " of " + this._records.length + " records";
        }
        return 'Showing 0 of 0';
    }
}

genericDataTableLWC.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>54.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>

Step 6: Create displayDataTable . This component is a parent component which will hold childDatatable and genericDataTableLWC.

displayDatatable.html
<template>
    <lightning-layout>
        <div class="c-container">
            <lightning-layout multiple-rows="false">
                <lightning-layout-item padding="around-small">
                    <c-child-datatable
                        fecth-object-name={objectNameDatatable}
                        onretreive={retriveRecordHandler}>
                    </c-child-datatable>
                </lightning-layout-item>
            </lightning-layout>
        </div>
    </lightning-layout>

    <template if:true={isRecordsVisible}>
        <lightning-card>
            <p class="slds-p-horizontal_medium">{objectNameDatatable} Records Using dynamic DataTable</p>
            <br></br>
            <c-generic-data-table-l-w-c
                    columns={columns}
                    records={data}
                    sortable= true
                    show-pagination=true
                    pagination-size="10"
                    hide-check-box="true">
            </c-generic-data-table-l-w-c>
        </lightning-card>
    </template>
</template>

displayDatatable.js
import { LightningElement, api } from 'lwc';
import retreieveRecords from '@salesforce/apex/dynamicDataTableController.retreieveRecords';
const sfdcBaseURL = window.location.origin;
export default class DisplayDatatable extends LightningElement {

  @api objectNameDatatable; // holding objectName value which is passed from App builder property
  @api fieldAPINames = ''; // holds list of fields API Name for the given object
  @api customSettingName;  // Custom Setting which will store all the fields selected
  tempColumn = []; //temporary column list to store columns
  data = []; // data to display on datatable
  columns; //final columns/headers of datatable
  newobj = {};
  newDataArray = [];
  defaultSortDirection = 'asc';
  isRecordsVisible = false;
  retObj = {};

  retriveRecordHandler(event) {
    this.resetDataTable();
    let args = JSON.parse(JSON.stringify(event.detail));
    this.fieldAPINames = args.selectedFieldsValue.toString();
    args.fieldAPIMapSelected2.forEach(itemVal => {
      if (itemVal.value.includes(".Name") && itemVal.value !== 'RecordType.Name') {
        this.tempColumn = [...this.tempColumn,
        {
          label: itemVal.label,
          fieldName: itemVal.value.replace(/\.+Name/g, 'URL'),
          type: 'url',
          typeAttributes: {
            label: {
              fieldName: itemVal.value.replace(/\./g, '')
            },
            target: '_blank'
          },
          sortable: true
        }];
      } else {
        this.tempColumn = [...this.tempColumn,
        {
          label: itemVal.label,
          fieldName: itemVal.value.includes(".Name") ? itemVal.value.replace(/\./g, '') : itemVal.value,
          sortable: true
        }];
      }
    });
    this.columns = this.tempColumn;
    this.retreieveRecordsfromApex();
  }


  retreieveRecordsfromApex() {
    console.log(' this.objectNameDatatable =>' + this.objectNameDatatable);
    console.log(' this.fieldAPINames =>' + this.fieldAPINames);
    retreieveRecords({
      objectName: this.objectNameDatatable,
      fieldAPINames: this.fieldAPINames
    })
      .then(data => {
        console.log(' data =>' + data);
        data.forEach(itemVal => {
          this.retObj = this.iterate(itemVal);
          if (Object.keys(this.retObj).length !== 0 && this.retObj.constructor === Object) {
            this.newDataArray = [...this.newDataArray, this.retObj];
          }
        });
        console.log(' this.newDataArray =>' + JSON.stringify(this.newDataArray));
        this.data = [...this.newDataArray];
        console.log(' this.data =>' + JSON.stringify(this.data) );
        if (this.data) {
          console.log(' inside if(this.data) ');
          this.isRecordsVisible = true;
          setTimeout(() => {
            this.updateGenericDatatable(this.data, this.columns);
         }, 100);
        }
        this.error = undefined;
      }).catch(error => {
        this.error = error;
        this.data = undefined;
      })
  }

  updateGenericDatatable(tablerecords,tablecolumns) {
    console.log('this.tablecolumns ' + JSON.stringify(tablecolumns));
    console.log('this.tablerecords ' + JSON.stringify(tablerecords));
    if (tablecolumns !== undefined) {
      this.updateColumns(tablecolumns);
      console.log('tablecolumns ' + tablecolumns);
    }
    if (tablerecords !== undefined) {
      this.updateRecords(tablerecords);
      console.log('tablerecords ' + tablerecords);
    }
  }

  updateColumns(columns) {
    if (this.template.querySelector('c-generic-data-table-l-w-c')) {
      this.template.querySelector('c-generic-data-table-l-w-c').updateColumns(columns);
    }
  }

  updateRecords(records) {
    if (this.template.querySelector('c-generic-data-table-l-w-c')) {
      this.template.querySelector('c-generic-data-table-l-w-c').refreshTable(records);
    }
  }


  iterate(obj) {
    this.newobj = {}; // reset newobj
    for (var property in obj) {
      // this if blocks check if the object has any property
      if (obj.hasOwnProperty(property)) {
        let xmlString = obj[property];
        // if block to all Reference field as URL clickable
        if (typeof xmlString === "object") {
          if (property !== 'RecordType') {
            this.newobj[property + 'URL'] = sfdcBaseURL + '/' + obj[property].Id;
          }
          this.newobj[property + 'Name'] = obj[property].Name;
        }
        // if block to make all Formula fields URL clickable
        else if (typeof xmlString === "string" && xmlString.includes('href')) {
          let doc = new DOMParser().parseFromString(xmlString, "text/xml");
          let PATTERN_FOR_EXTERNAL_URLS = /^(\w+:)?\/\//;
          let href = doc.querySelector('a').getAttribute('href');
          //alert("href.search(PATTERN_FOR_EXTERNAL_URLS)" +href.search(PATTERN_FOR_EXTERNAL_URLS));
          if (href !== undefined && href.search(PATTERN_FOR_EXTERNAL_URLS) !== -1 || href.includes('www'))
            this.newobj[property + 'URL'] = href;
          else {
            this.newobj[property + 'URL'] = sfdcBaseURL + href;
          }
          this.newobj[property + 'Name'] = doc.firstChild.innerHTML;
        }
        // this if block is to all all other fields values/property and to stop adding the RecordId to the final object
        else if (property !== 'Id') {
          this.newobj[property] = obj[property];
        }
      }//if
    }//for loop
    return this.newobj;
  }// iterate fnc

  // to reset the DataTable
  resetDataTable() {
    this.tempColumn = [];
    this.data = [];
    this.columns = [];
    this.newobj = {};
    this.newDataArray = [];
  }
}

displayDatatable.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>51.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage,lightning__AppPage,lightning__HomePage">
            <property name="objectNameDatatable" label="Object API Name to Retrieve" type="String" default=""/>      
        </targetConfig>        
    </targetConfigs>
</LightningComponentBundle>

After creating all the components add the component on your page. Here for example I have added the component for three different objects on same page to see the functionality.
  1. Account
  2. Contact
  3. Student__c (Custom Object)
Steps to add the component on page
1. Go to Set up
2. In quick find search Lightning App Builder and click.
3. Select the page you want to add the component in and Click Edit.



4. Search the component on the left search panel and then drag & drop the component.
5. Click Save and go back to the page.



Demo:



Full code:- GitHub Link

Resources:
Salesforce Components Library


Thank you all !
#Keep learning #Keep Sharing :)



Recent blogs:
Custom Toast Message in LWC
Generic Modal Box in LWC
How to use hyperlink in custom Toast Notification message in LWC
Nested Accordion in LWC
Build custom add to favorites functionality using LWC



Comments

Popular posts from this blog

How to update Field Level Security in bulk ?

Assign Field Level Security(FLS) in Salesforce Demo:- In this blog we will see the easy way to update Field Level Security(FLS) for Permission sets/Profiles in bulk. I have created a tool to make the Admins job a little easier. Before jumping to tool let us see the different standard approach we follow to update FLS in Salesforce.                     As an admin we usually update FLS from salesforce setup/UI. If we are working on a new Application with many Permission sets and Objects with 100+ fields it can be very time consuming task. Using this tool we can assign FLS in just few mins. Let us take an example. Suppose we have a new application and we have created 20 Permission sets and 8 Profiles . Now there are 10 Objects in all and in each object we have 15 fields for which we need to update FLS for above Permission sets and Profiles. Let us first see the different approach to assign FLS in Salesforce. We have two ways in which we can update FLS for a particular field.  1 .To upd

How to create custom polymorphic field using LWC ?

How to create custom polymorphic field using LWC ? In this blog we will see how we can make polymorphic lookup field using LWC. What is Polymorphic field? A polymorphic field is one where related object might be one of the several different types of objects. There can be a use case where a customer wants to connect one object with multiple objects- i.e, relationships between objects or relate a child object to multiple parents's ojects. For example, in task Object we have three such polymorphic fields. The WhoId(Name) relationship of Task can be with Contact or a Lead. Assigned To field can be a User or a Queue. Similarly, a WhatId(Related To) relationship field of Task can be with many other objects like Accounts, Opportunities etc. In Salesforce, currently we do not have any OOTB option or may be we can say we do not have a datatype for polymorphic field which we can create but if required we can create a custom component to facilitate the same functionality. So let's get

How to use Hyperlink in Custom Toast Notification message using LWC

  Use Hyperlink in Custom Toast Notification message using LWC   We all know that a component can send a toast notification that pops up to alert users of a success, error, or warning. A toast can also simply provide information to the user. But what if we need a hyperlink on the message to navigate to the records on the message body. Yes, we can put Links as well on the message body as we can see in Standard Notification Toasts. This is very simple just we need to remember few things. We should know how to use Navigation Services by using NavigationMixin.GenerateUrl method. Let us see the functionality in this blog. To display a toast notification in Lightning Experience or Experience Builder sites we import ShowToastEvent from the lightning/platformShowToastEvent module. We use this ShowToastEvent to provide feedback to a user following an action, such as after a record is created. Now in this blog we will see how to add hyperlink in the message of the Show Toast event and navig