The enterprises have much admired the low code approach for customization needs for any customization within the Salesforce. And one of the main reasons behind Salesforce becoming an industry leader.
As a developer myself, we tend to jump on a custom solution for any business requirement. Salesforce recommends Click, Not code approach as a best practice to utilize the best value of the native functionality the platform has to offer. In Salesforce, every written line of custom code will require to maintain. Any modification, regardless of how small it is, will need a developer’s engagement as Admin does not generally have the skills to modify custom-developed code.
As we all know, it isn’t a cost-effective approach to engage developers for small requirement modification. Therefore when developing a custom solution, we should always look for an opportunity to develop in a way that can be easily maintained by Admin without code knowledge.
I am not by any means trying discourage you from developing a custom code in salesforce. Instead of asking to be considerate about Admins in your team when developing any custom code.
Let’s have a look at the example of page layout in Salesforce. As we are all aware, page layouts sometimes can become very lengthy for specific needs with all fields and sections available on the page layout.
Issue with Flow screen component and field elements
Let’s say you need to load a record page within the flow screen. Although the Flow screen has components for fields and sections, it’s a lot of hassle to add each element to the screen and map it back to the appropriate field. Even after all that, you will not receive the desired result as the flow doesn’t support multiple columns in a row. Reference screenshot below to view the screen component with fields added.

Developers within ourselves, when presented with such a scenario would want to jump on developing custom components to deliver the idle user experience. Not necessarily always keeping maintainability issue for small requirement changes like
- A new required field on the page layout
- Rearrangement of the fields and section on the page layout
- Renaming field label
Solution using lightning component
In the example below, we will review the lightning component that can be easily modified by admin without having any coding knowledge. With little to no efforts on the component, the below code will automatically pull what is available on the page layout, including standard ad custom fields sections and labels of the fields.
Aura Component
<aura:component implements="flexipage:availableForAllPageTypes" access="global" controller="DynamicPageLayout">
<aura:attribute name="disabled" type="Boolean" default="false" />
<aura:attribute name="layoutSections" type="List" />
<aura:attribute name="saved" type="Boolean" default="false" />
<aura:attribute name="showSpinner" type="Boolean" default="true" />
<aura:attribute name="fieldName" type="String" default="StageName" />
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<lightning:card title="">
<aura:if isTrue="{!v.showSpinner}">
<lightning:spinner />
</aura:if>
<aura:if isTrue="{!!v.saved}">
<lightning:recordEditForm
onload="{!c.handleLoad}"
onsubmit="{!c.handleSubmit}"
onsuccess="{!c.handleSuccess}"
objectApiName="Contact">
<lightning:messages />
<aura:iteration items="{!v.layoutSections}" var="section">
<div class="slds-section slds-is-open">
<h3 class="slds-section__title">
{!section.label}
</h3>
<div class="slds-section__content">
<lightning:layout multipleRows="{!section.totalColumns > 1 }">
<aura:iteration items="{!section.lstFields}" var="field">
<lightning:layoutItem size="{! 12/section.totalColumns }" flexibility="auto" padding="around-small">
<aura:if isTrue="{!(!field.isReadOnly)}">
<lightning:inputField fieldName="{!field.fieldName}" />
<aura:set attribute="else">
<lightning:outputField fieldName="{!field.fieldName}" />
</aura:set>
</aura:if>
</lightning:layoutItem>
</aura:iteration>
</lightning:layout>
</div>
</div>
</aura:iteration>
<lightning:layout verticalAlign="center" class="x-large">
<lightning:layoutItem padding="around-large">
<div class="slds-m-top_medium">
<lightning:button disabled="{!v.disabled}" variant="brand" type="submit" name="save" label="Save" />
</div>
</lightning:layoutItem>
</lightning:layout>
</lightning:recordEditForm>
<aura:set attribute="else">
<p>Saved!</p>
</aura:set>
</aura:if>
</lightning:card>
</aura:component>
Controller
({
doInit: function( component, event, helper ) {
var action = component.get("c.getPageLayoutFields");
action.setCallback(this, function(response) {
var state = response.getState();
if (state === "SUCCESS") {
component.set("v.layoutSections", response.getReturnValue() );
console.log( response.getReturnValue() );
}
else if (state === "INCOMPLETE") {
}
else if (state === "ERROR") {
var errors = response.getError();
console.log( errors );
}
});
$A.enqueueAction(action);
},
handleSuccess : function(component, event, helper) {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
"title": "Success!",
"message": "Updated",
"type": "success"
});
toastEvent.fire();
},
handleLoad : function(component, event, helper) {
component.set("v.showSpinner", false);
},
handleSubmit : function(component, event, helper) {
}
})
Apex Class
public class DynamicPageLayout {
@AuraEnabled
public static List<LayoutSection> getPageLayoutFields() {
List<LayoutSection> lstSections = new List<LayoutSection>();
try {
List<String> componentNameList = new List<String>{'Contact-Contact Layout'};
List<Metadata.Metadata> components = Metadata.Operations.retrieve(Metadata.MetadataType.Layout, componentNameList);
Metadata.Layout contLayout = (Metadata.Layout) components.get(0);
for( Metadata.LayoutSection ls : contLayout.layoutSections ) {
LayoutSection section = new LayoutSection( ls.label, ls.layoutColumns.size() );
List<LayoutColumn> lstColumns = new List<LayoutColumn>();
Integer maxFieldsInColumn = 0;
for( Metadata.LayoutColumn lc : ls.layoutColumns ) {
LayoutColumn column = new LayoutColumn();
if( lc.layoutItems != null ) {
if( maxFieldsInColumn < lc.layoutItems.size() ) {
maxFieldsInColumn = lc.layoutItems.size();
}
for( Metadata.LayoutItem li : lc.layoutItems ) {
column.lstFields.add( new LayoutField( li ) );
}
}
if( column.lstFields.size() > 0 ) {
lstColumns.add( column );
}
}
if( maxFieldsInColumn > 0 ) {
for( Integer i = 0; i < maxFieldsInColumn; i++ ) {
for( Integer j = 0; j < lstColumns.size(); j++ ){
if( lstColumns[j].lstFields.size() > i ) {
section.lstFields.add( lstColumns[j].lstFields[i] );
}
else {
section.lstFields.add( new LayoutField() );
}
}
}
}
lstSections.add( section );
}
}
catch( Exception e ){
System.assert(false, e.getLineNumber() + ' : ' + e.getMessage() );
}
return lstSections;
}
public class LayoutSection {
@AuraEnabled public String label;
@AuraEnabled public List<LayoutField> lstFields;
@AuraEnabled public Integer totalColumns;
public LayoutSection( String label, Integer totalColumns ) {
this.label = label;
this.totalColumns = totalColumns;
this.lstFields = new List<LayoutField>();
}
}
private class LayoutColumn {
private List<LayoutField> lstFields;
public LayoutColumn() {
this.lstFields = new List<LayoutField>();
}
}
public class LayoutField {
@AuraEnabled public String fieldName;
@AuraEnabled public Boolean isRequired;
@AuraEnabled public Boolean isReadOnly;
public LayoutField() {}
public LayoutField( Metadata.LayoutItem li ) {
this.fieldName = li.field;
if( li.behavior == Metadata.UiBehavior.Required ) {
this.isRequired = true;
}
else if( li.behavior == Metadata.UiBehavior.ReadOnly ) {
this.isReadOnly = true;
}
}
}
}