13

@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.

1

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!

  • Thanks for the suggestion! The only reason i did not do that is, it did not provide much support for "Events" [force:inputField] . Currently only 1 event is supported [ie "change" event ] which might be a deal breaker if we wanted to extend this component further in future. In contrast, ui:inputField provides native support for 19 events. – Jag Apr 18 '16 at 16:42
0

Just wanted to close the loop on this question.I was not able to find a better way to do this!

We took the expensive route of creating custom AURA components (Ie, UI:InputTextBox, UI:InputPhone etc..) and used them in our forms. The other reason why we did not use force:inputField is, it did provided limited OOTB support for Events.

The use-case to use fieldSets to create forms are to circumvent the usage of RecordTypes as there can be more than 200+ combinations of different Form Layouts.

Hope this helps someone!

0

Have you looked into lightning:inputField? It seems to do exactly what you're looking for.

https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/aura_compref_lightning_input.htm

https://developer.salesforce.com/blogs/2018/01/spring-18-developers-build-secure-interactive-forms-faster.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.