[SalesForce] Dynamic Component Creation using “force:inputField”

@SkipSauls wrote an amazing article on how to use FieldSets in Lightning Components. Thank you!

Link here

But it seems like, using this approach, we can only use OOTB aura components (ui:textbox, ui:button) . But these components are not compatible with Lightning Design System.

Hence i started creating generic custom components (c:customComponent) and used it when creating dynamic components using $A.createComponent(….).

But creating generic components (with LDS Support) which supports all different types of metadata seems to consume enormous time.

Is there a better way to approach it?

I am trying to see if there is a better way such as usage of "force:inputfield" inside $A.createComponent(…) so that Salesforce Platform can determine and render components using respective metadata automatically.

Have anyone tried this approach ? It doesn't seem to work for me.

Here is my first Try (This works, but too painful and time consuming to build all generic components).

PageLayoutManager.cmp

<aura:component access="GLOBAL" controller="PageLayoutManagerApexController"
                implements="force:appHostable,flexipage:availableForAllPageTypes,force:hasRecordId">

    <!-- Public Page Attributes -->
    <aura:attribute name="fieldSetName" type="String" access="global"/>
    <aura:attribute name="lazyLoad" type="Boolean" default="true" access="global"/>
    <aura:attribute name="totalColumns" type="Integer" access="global"/>
    <aura:attribute name="dataTransportMode" type="String" access="global" 
                                                           description="Enables to pass-in values by data-wrapper [OR] 
                                                                        by providing fieldSet Name. 
                                                                        Accepted Values: (FieldSet [OR] DataWrapper)" />

    <!-- Private Page Attributes -->
    <aura:attribute name="fieldWrapperList" type="object" access="private" />

    <!-- Context Aware Attributes -->
    <aura:attribute name="recordId" type="String" default="a0Dg000000636rzEAA" />
    <aura:attribute name="sObjectName" type="String" default="KYCOnboard__c"/>

    <!-- Page Handlers -->
    <aura:handler name="init" value="{!this}" action="{!c.onInit}" />

    <!-- Future Functionality -->
    <aura:attribute name="dataWrapper" type="Object" />

    <!-- Dynamic UI Markup -->
    <div class="slds">
        <div class="slds-col">
            <aura:iteration items="{!v.body}" var="bodyElement">
                <div class="slds-form-element">
                    {!bodyElement}
                </div>
            </aura:iteration>
        </div>
    </div>

</aura:component>

PageLayoutManagerController.js

({
    onInit : function(component, event, helper) {
        console.log("PageLayoutManager -- onInit() -- ENTRY");
        helper.getFieldSetMetadata(component, helper);
        console.log("PageLayoutManager -- onInit() -- EXIT");
    }
})

PageLayoutManagerHelper.js

({
    getFieldSetMetadata : function(component, helper) {
        console.log("PageLayoutManager -- helper.getFieldSetMetadata() -- ENTRY");

        var SObjectName = component.get("v.sObjectName");
        var FieldSetName = component.get("v.fieldSetName");
        var RecordId = component.get("v.recordId");

        //Getting Apex Method Instance        
        var action = component.get("c.getApexFieldSetMetadata");

        if(SObjectName && FieldSetName){
            action.setParams({
                sObjectName : SObjectName,
                fieldSetName : FieldSetName,
                recordId : RecordId
            });

            $A.enqueueAction(action);
            console.log('getFieldSetMetadata() - Request Initalized to Apex Controller ');

            var self = this;

            //Handle Response back from Apex Controller.
            action.setCallback(self, function(response){
                var callbackState = response.getState();
                var fieldWrapperList = response.getReturnValue();

                if(callbackState === "SUCCESS"){
                    console.log('getFieldSetMetadata() - Response...');
                    console.table(response.getReturnValue());
                    component.set("v.fieldWrapperList", fieldWrapperList);
                    helper.constructDynamicComponents(component, helper, fieldWrapperList);
                }else if(callbackState === "ERROR"){
                    console.log('Something went wrong!');
                }
            });
        }
        console.log("PageLayoutManager -- helper.getFieldSetMetadata() -- EXIT");
    },

    constructDynamicComponents : function(component, helper, fieldWrapperList){
        console.log("PageLayoutManager -- helper.constructDynamicComponents() -- ENTRY");

        var componentTypeConfigMap = JSON.parse(JSON.stringify(this.componentTypeConfigObj));
        var componentAttributesConfigMap;

        for(var i = 0; i < fieldWrapperList.length; i++){

             componentAttributesConfigMap = {};
            //Configure Attributes
            componentAttributesConfigMap["aura:id"] = component.getGlobalId();
            componentAttributesConfigMap["label"] = fieldWrapperList[i].label;
            componentAttributesConfigMap["access"] = "Global";

            //Component Specific Attributes
            if(fieldWrapperList[i].type == "STRING"){
                componentAttributesConfigMap["class"] = "slds-input";
            }else if(fieldWrapperList[i].type == "DATETIME"){
                componentAttributesConfigMap["class"] = "slds-input";
                componentAttributesConfigMap["displayDatePicker"] = "true";
            }else if(fieldWrapperList[i].type == "REFERENCE"){
                componentAttributesConfigMap["class"] = "slds-input";
            }

            //Create Dynamic Components
            //Syntax: createComponent(String type, Object attributes, function callback) 
            $A.createComponent(
                componentTypeConfigMap[fieldWrapperList[i].type],
                componentAttributesConfigMap,
                function(dynamicCmp){
                    if(component.isValid()){
                        var body = component.get("v.body");
                        body.push(dynamicCmp);
                        console.log(body);
                        component.set("v.body", body);
                    }   
                }
            );
        }
        console.log("PageLayoutManager -- helper.constructDynamicComponents() -- EXIT");
    },

    componentTypeConfigObj: {
        "DATETIME" : "c:GenericInputDate",
        "REFERENCE" : "ui:inputText",
        "STRING" : "c:GenericInputTextBox"
    }
})

PageLayoutManagerApexController.aspx

public class PageLayoutManagerApexController {

    /* PUBLIC METHODS */
    @AuraEnabled
    public static List<fieldSetWrapper> getApexFieldSetMetadata(String sObjectName, String fieldSetName, String recordId){

        Map<String, Schema.FieldSet> fieldSetMap = new Map<String, Schema.FieldSet>();

        /*
            To Do: Since the component is within <aura:iteration>, getAllFieldSets() would be called
            several items. Hence move the "fieldSetMap" to Platform Cache -- Jag
        */

        if(!String.isBlank(sObjectName)){
            fieldSetMap = getAllFieldSets(sObjectName);
        }

        //Exit quickly, if no fieldSet is found associated to sObject
        if(fieldSetMap.size() > 0){
            return getFieldMetadata(fieldSetMap, fieldSetName);
        }else{
            return null;
        }
    }

    /* PRIVATE METHODS */
    private static Map<String, Schema.FieldSet> getAllFieldSets(String sObjectName){
        //Get sObject in Schema.SObjectType format
        Schema.SObjectType sObjectType = Schema.getGlobalDescribe().get(sObjectName);

        //Then, get sObjectResult which has all the metadata info. about sObject.
        Schema.DescribeSObjectResult describesObjectRes = sObjectType.getDescribe();

        //sObject Result Schema has info about fieldSets. Store it in a Map.
        Map<String, Schema.FieldSet> fieldSetMap = describesObjectRes.fieldSets.getMap();

        System.debug('FieldSet Map: ' + fieldSetMap);
        return fieldSetMap;
    }

    private static List<fieldSetWrapper> getFieldMetadata(Map<String, Schema.FieldSet> fieldSetMap, String fieldSetName){
        List<Schema.FieldSetMember> fieldSetMemberList = new List<Schema.FieldSetMember>();

        if(!String.isBlank(fieldSetName)){
            Schema.FieldSet fieldSet = fieldSetMap.get(fieldSetName);
            if(!String.isBlank(String.valueOf(fieldSet))){
                fieldSetMemberList = fieldSet.getFields();
            }
            System.debug('FieldSet Members : ' + fieldSetMemberList);

            if(fieldSetMemberList.size() > 0){
                List<fieldSetWrapper> fswList = new List<fieldSetWrapper>();
                for(Schema.FieldSetMember fsm: fieldSetMemberList){
                    fieldSetWrapper fsw = new fieldSetWrapper(fsm);
                    fswList.add(fsw);
                }
                if(fswList.size() > 0){
                    return fswList;
                }else{
                    return null;
                }
            }else{
                return null;   
            }
        }else{
            return null;  
        }
    }

    /*
        Wrapper Class
    */
    public class fieldSetWrapper{

        @AuraEnabled
        public Boolean DBRequired {get; set;}

        @AuraEnabled
        public String fieldPath {get; set;}

        @AuraEnabled
        public String label {get; set;}

        @AuraEnabled
        public Boolean required {get; set;}

        @AuraEnabled
        public String type {get; set;}

        public fieldSetWrapper(Schema.FieldSetMember fieldSetMember){
            this.DBRequired = fieldSetMember.getDbRequired();
            this.fieldPath = fieldSetMember.getFieldPath();
            this.label = fieldSetMember.getLabel();
            this.required = fieldSetMember.getRequired();
            this.type = String.ValueOf(fieldSetMember.getType());
        }
    }

    /* FieldSetMember Structure Reference */
    /*
        FieldSet Members: (
            Schema.FieldSetMember[
                getDbRequired = false; 
                getFieldPath = KYC_Risk_Officer__c; 
                getLabel = KYC Risk Officer; 
                getRequired = false; 
                getType = REFERENCE;
            ], 
            Schema.FieldSetMember[
                getDbRequired = false; 
                getFieldPath = Name; 
                getLabel = KYCOnboard Name; 
                getRequired = false; 
                getType = STRING;
            ]
        )
    */
}

Console Log Output

enter image description here

Here is the piece of code i am trying to make it work

for(var i = 0; i < fieldWrapperList.fswList.length; i++){
            $A.createComponent(
                "force:inputField",
                {
                    "aura:id" : component.getGlobalId(),
                    "label" : fieldWrapperList.fswList[i].label,
                    "value" : fieldWrapperList.sObjectInstance.KYC_Risk_Officer__c
                },
                function(dynamicCmp){
                    if(component.isValid()){
                        var body = component.get("v.body");
                        body.push(dynamicCmp);
                        console.log(body);
                        component.set("v.body", body);
                    }   
                }
            );
        }

Any help would be appreciated!
Thank you.

Best Answer

Have you tried to extend force:inputField component?

From what I'm seen on Aura Documentation, force:inputField is abstract and extensible. If you extend this component you can use renderers to change the output DOM and assign LDS classes.

I don't know if this ok to do, I haven't found anything around avoiding extending standard components.

Hope this works!

Related Topic