Friday, June 12, 2020

Reusable/Configurable Data table in Lightning Web Component (LWC) using TargetConfigs Attributes

Thought Process behind this implementation


In Past month we got almost two same requirements to showcase list of records based on certain criteria. Salesforce has their in built feature , but there is limitation of showcasing maximum 6 records at a time and to show all the records user has to click one more time. So minimize the User efforts and make it handy for Admin as well, this component has been built which can be reuse in any place from Home Page,Record page and in App Page as well.


Use Case


In the Home Page User want to see the list of opportunities which has the profitability greater that 10% . The list of fields would be Name, Stage Name, Profitability, Close Date.

And the Same user want to see the list of opportunities in the Record Page , where only Closed Won opportunities will be shown. The list of fields would be Name, Type and Lead Source.

Expected Outcome






    How Architecture works behind


       1.     Dynamically Populated the List of Objects using Global class by extending                  
               VisualEditor.DynamicPicklist.
       2.     Create LWC component which Includes :
    1.     html File that will help you to show the dynamic data table.
    2.     Java Script file that will help you build the Logic.
    3.     Each Lightning web component folder must include a configuration file named <component>.js-meta.xml. In which there is attribute called  "targetConfigs" help you to Configure the component for different page types and define component properties.

        3.   Create a class to prepare list the data which needs to be shown based on certain attributes.

    Process to implement


    • As discussed First the Global class needs to created which will prepare and return the list of Object currently present in the Environment. Class name is ObjectList
       Code as follows :


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    global class ObjectList extends VisualEditor.DynamicPickList{
        //Default method which can be ignored , kept that for your better understanding of stucture of VisualEditor Namespace
        global override VisualEditor.DataRow getDefaultValue(){
            VisualEditor.DataRow defaultValue = new VisualEditor.DataRow('red', 'RED');
            return defaultValue;
        }
        
        //Prepare the list of Object and return
        global override VisualEditor.DynamicPickListRows getValues() {
            Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
            
            VisualEditor.DynamicPickListRows  objValues = new VisualEditor.DynamicPickListRows();
            List<String> entities = new List<String>(schemaMap.keySet());
            entities.sort();
            for(String name : entities)
            {
                objValues.addRow( new VisualEditor.DataRow(name, name));
            }     
            return objValues;
        }
    }
       
    Few points to be remembered :
    • In getValues method we preparing the list of object with the help of salesforce in-build method by extending the VisualEditor Namespace which provides classes and methods for interacting with the Lightning App Builder, in our POC we will be using it in meta.xml file in some time.

    1
    global class ObjectList extends VisualEditor.DynamicPickList{
    


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
     global override VisualEditor.DynamicPickListRows getValues() {
            Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
            
            VisualEditor.DynamicPickListRows  objValues = new VisualEditor.DynamicPickListRows();
            List<String> entities = new List<String>(schemaMap.keySet());
            entities.sort();
            for(String name : entities)
            {
                objValues.addRow( new VisualEditor.DataRow(name, name));
            }     
            return objValues;
        }
    
    Another class needs to be created as follows :

    Preview:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    /*
    Class Name : ReUsecls
    Developer Name : Avijit Das
    */
    
    public with sharing class ReUsecls {
        public ReUsecls() {
        }
     /*
      Method Name : getFieldTypes
      Input Parameters : List of Columns Name, Object Name.
      OutPut Parameter : Map<String, List<String>> | Api Name=(data type,Label Name) | example : MainCompetitors__c=(STRING, Main Competitor(s))
      purpose : It will taka Object and Columns name as input parameter and by using Schema.getGlobalDescribe preparing the Map as utput already given with the example.
     */
        @AuraEnabled
        public static Map<String, List<String>> getFieldTypes(String selectedFields,String obj){
            String objectName = obj;
            List<String> lst_str = new List<String>();
            lst_str = selectedFields.split(',');
            Map<String, List<String>> fieldTypeMap = new Map<String,  List<String>>();
    
            for(String field : lst_str){
                List<String> lst_objtypelab  = new List<String>();
                Schema.DisplayType fieldType = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().get(field).getDescribe().getType();
                lst_objtypelab.add(String.valueOf(fieldType));
                lst_objtypelab.add(String.valueOf(String.valueOf(Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().get(field).getDescribe().getLabel())));
                fieldTypeMap.put(field, lst_objtypelab);
            }
            return fieldTypeMap;
        }
    
     /*
      Method Name : getObjectRecords
      Input Parameters : List of Columns Name, Object Name, where contions
      OutPut Parameter : List<sObject> as it might be any object
      purpose : It will taka Object,Columns name and filtered conditions (optional) as input parameter and after forming the query retunr the list of records
     */
        @AuraEnabled(cacheable=true)
        public static List<sObject> getObjectRecords(List<String> selectedFields,String obj,String whcon){
            System.debug('Selected---'+selectedFields+'---'+obj);
            String stemp = '';
            Integer i = 0;
            String query ='';
            for(String field : selectedFields){
                if(i == 0){
                    stemp+= field;
                }else{
                    stemp+= ','+field;
                }
                i++;
    
            }
            query = 'SELECT ' + stemp + ' FROM '+obj;
            if(whcon!=''){
                query = query+' where '+whcon;
            }
            return Database.query(query);
        }    
    
    }
    

    All the functions has been described in comment section.

    meta.xml file as follows : 

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?xml version="1.0" encoding="UTF-8"?>
    <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
        <apiVersion>48.0</apiVersion>
        <isExposed>true</isExposed>
        <targets>
            <target>lightning__AppPage</target>
            <target>lightning__RecordPage</target>
            <target>lightning__HomePage</target>
        </targets>
        <targetConfigs>
            <targetConfig targets="lightning__HomePage,lightning__RecordPage">
                <property name="objName" type="String" datasource="apex://ObjectList" label="Object Name"/>
                <property name="fldShn" type="String" default="" label="Fields To be shown"/>
                <property name="whcon" type="String" default="" label="Condition"/>
            </targetConfig>
        </targetConfigs>
    </LightningComponentBundle>
    

    For this POC meta.xml file is pretty important. Points to be remembered :
    • In targetConfigs target attribute we are specifying one or more page type where we will be using the component, In the POC we are trying to configure the component such a way that we can use it in Home page and record page as well.
    1
     <targetConfig targets="lightning__HomePage,lightning__RecordPage">
    

    • By Using the property attribute we are defining the input parameters for the Component author to be given at the time of adding the component in the Pages and based on the input parameter the component should work dynamically. There are three properties will be needed 
      • Object which will be pre-populated dynamically by using the visual editor and can be used by datasource attribute "datasource="apex://ObjectList""
      • Number of fields which will reflect as column .
      • Where condition, based on which we will be filtering the data.
    <targetConfig targets="lightning__HomePage,lightning__RecordPage">
                <property name="objName" type="String" datasource="apex://ObjectList" label="Object Name"/>
                <property name="fldShn" type="String" default="" label="Fields To be shown"/>
                <property name="whcon" type="String" default="" label="Condition"/>
            </targetConfig>


    Js file as follows


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    /*
    Name : ReUse
    Developer Name : Avijit Das
    */
    
    import { LightningElement, track, wire, api } from 'lwc';
    import getObjectRecords from '@salesforce/apex/ReUsecls.getObjectRecords';
    import getFieldTypes from '@salesforce/apex/ReUsecls.getFieldTypes';
    
    
    export default class ReUse extends LightningElement {
        @api objName;
        @api fldShn;
        @api whcon;
     
     //default Id and Name will be assigned if nothing has been given.
        @track columns = [
            { label: 'Id', fieldName: 'Id' },
            { label: 'Name', fieldName: 'Name' } 
        ];
     
     //Passing the default columns
        @track selected = ['Id', 'Name'];
        @track fetchedRecords = [];   
    
     //based on the chnages in the input parameters in the Meta.xml file the functions will fire
        @wire(getObjectRecords, { selectedFields: '$fldShn', obj: '$objName', whcon: '$whcon'})
        wiredContacts({ error, data }) {
            if (data) {
                this.fetchedRecords = data;
       //This will prepare the list of columns
                this.handleFetchFieldTypes();            
            } else if (error) {
                console.log(error);
            }
        }
    
     
        handleFetchFieldTypes() {        
            var fieldarry  = this.fldShn.split(',');
            this.selected.splice(0, this.selected.length)
      
      //Prepare the input in the form of string array  
            for (var j = 0; j < fieldarry.length; j++) {
                console.log(fieldarry[j]);
                this.selected.push(fieldarry[j]);
            }
    
      //once the functions has been called it will return Map<String, List<String>>
            getFieldTypes({ selectedFields: this.fldShn, obj: this.objName})
                .then(result => {
                    this.columns = this.selected.map(field => {
                        
         //assigning the data type
         const dType = result[field][0];
         
         //assigning the lable
                        const dlabel = result[field][1];
                        if (dType === 'STRING' || dType === 'ID') {
                            return { label: dlabel, fieldName: field };
                        } else if (dType === 'DATE') {
                            return { label: dlabel, fieldName: field, type: 'date' };
                        } else if (dType === 'DATETIME') {
                            return {
                                label: dlabel,
                                fieldName: field,
                                type: 'datetime'
                            };
                        } else if (dType === 'Integer') {
                            return {
                                label: dlabel,
                                fieldName: field,
                                type: 'Integer'
                            };
                        } else if (dType === 'BOOLEAN') {
                            return {
                                label: dlabel,
                                fieldName: field,
                                type: 'text'
                            };
                        } else {
                            return { label: dlabel, fieldName: field };
                        }
                    });
                })
                .catch(error => {
                    console.log(error);
                });
                
        }
    
    }
    


    html code as follows :


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <template>
        <lightning-card title="ReUsable Component" icon-name="custom:custom19">
            <lightning-datatable
                key-field="id"
                data={fetchedRecords}
                columns={columns}
                >
            </lightning-datatable>  
        </lightning-card>
        
    </template>
    


    References: