Dynamic Fields With Record-Edit-Form in LWC

SoomjeetSahoo
4 min readNov 7, 2021

So on one fine day I was presented with an requirement to create a form basically a Record Edit Form in LWC. But that got me thinking as to how I could make the required fields dynamic. Then this made me go for a Record-Form which takes care of everything and just requires us to fill in the sobject-api-name and record-type-id.

But then came the other requirement of populating some fields prior to form onload. That’s where Record-Form stumbled. Tried every possible way that included event manipulation but since event object onload is populated after the form is loaded there is no way to push it back to the record form.

So Two Challenges to be precise:
1. Need to show all fields for the page layout for a given profile’s Record Type.
2. Need some fields to be Auto-Populated.

Solution for Problem1>
for this we need to query from “ProfileLayout” object via tooling api which will give us Page Layout name that’s based on the Record Type id and current user’s profile Id.

private static String restGet(String endPoint, String method, String sid) {System.debug('ep :'+endPoint);
Http h = new Http();
HttpRequest hr = new HttpRequest();
hr.setHeader('Authorization', 'Bearer ' +sid);
hr.setTimeout(60000);
hr.setEndpoint(endPoint);
hr.setMethod(method);
HttpResponse r = h.send(hr);
System.debug('Body > '+r.getBody());
return r.getBody();
}private static String toolingAPISOQL( String query) {String sessId = UserInfo.getSessionId().substring(15);
String baseURL='callout:ToolingApiQuery?'; //Named Credential
return restGet( baseURL +'q='+ (query.replace(' ', '+')),'GET', sessId);
}@AuraEnabled
public static String getLayoutNameForCurrentUserProfile( Id rtId ) {
String body = toolingAPISOQL('select Layout.Name from ProfileLayout where ProfileId = \'' + UserInfo.getProfileId() + '\' AND RecordTypeId = \'' +rtId+ '\'');//Gets the PagelayoutName
String name = body.substringBetween('"Name":"', '"');
System.debug('@@@ PageLayout Name ' + name );
return name;
}

You might observe the Named Credential I used in the above code snippet i.e. due to Apex Rest Limitations that blocks the calling current org’s rest services in LWC. For that I had to go the Connected App way for authenticating. To know more please follow the tutorial provided in the link .

P.S. Before setting up Named Credential Please be sure to create a Remote Site Settings with your current org domain url or else when the org tries to Authenticate it may throw the url_mismatch error something like below. 😊

error=redirect_uri_mismatch&error_description=redirect_uri%20must%20match%20configuration access

Solution for Problem2>

Now that we got the page layout name, we need to get all the field’s api names on that layout so that we could loop over it inside a Record-Edit-Form using for:each inside a div or template tag.

@AuraEnabledpublic static List<String> fetchFieldsForRecordType(string sObjectApiName,String pageLayoutName){List<String> allFields = new List<String>();try {
List<Metadata.Metadata> layouts = Metadata.Operations.retrieve(Metadata.MetadataType.Layout, new List<String> {sObjectApiName+'-'+pageLayoutName});
Metadata.Layout layoutMd = (Metadata.Layout)layouts.get(0);for (Metadata.LayoutSection section : layoutMd.layoutSections) {
for (Metadata.LayoutColumn column : section.layoutColumns) {
if (column.layoutItems != null) {
for (Metadata.LayoutItem item : column.layoutItems) {
System.debug(item.field);
allFields.add(item.field);
}
}
}
}
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
return allFields;
}

In the above code snippet, I used the metadata class to retrieve the proper field api names used for that layout. I basically followed the solution from this link.

Now that we got all the fields lets now create an Java-script object which will take care of what we want to pass to UI.

import { LightningElement } from 'lwc';import fetchFieldsForRecordType from '@salesforce/apex/RecEditLWCApex.fetchFieldsForRecordType';import getLayoutNameForCurrentUserProfile from '@salesforce/apex/RecEditLWCApex.getLayoutNameForCurrentUserProfile';export default class RecEditTestLWC extends LightningElement {sObjectApiName = "Contact";
selectedRecordType = "0122x0000010nraAAA";//TODO: Remove Hardcode
pageLayoutName = '';
isLoaded = false;
allFields = [];
ignoreFields = ["CreatedById","LastModifiedById"];
connectedCallback() {
console.log("Connected-Callback");
this.getPageLayoutName();}getAllFields(resp){/*Fetches all the fields for the pagelayout*/
fetchFieldsForRecordType({
sObjectApiName : this.sObjectApiName,
pageLayoutName : resp
}).then((response) => {
let fields2push = [];
if(response){
this.isLoaded = true;
response.forEach((field,idx) => {
/*"ignoreFields" object has all the fields that we need to ignore from UI */
if (! (Object.values(this.ignoreFields).indexOf(field) > -1)) {
fields2push.push(this.fieldVal(field,""));//Please pass value u wnt
}
})
this.allFields = fields2push;
}
})
}
/*Makes an object for each field that needs to be placed in UI*/
fieldVal(field_name,value){
var obj = {};
obj.FieldName = field_name;
obj.FieldValue = value;
return obj;
}
getPageLayoutName(){
/* Fetches the pagelayout for the given recordtypeId "this.selectedRecordType" */
getLayoutNameForCurrentUserProfile({rtId : this.selectedRecordType})
.then((response) => {
console.log("Resp <getLayoutNameForCurrentUserProfile>: "+response);
this.pageLayoutName = response;
this.getAllFields(response);
}).catch((err) => {
console.error('OUTPUT: ',err);
})
}
}

So now lets frame the UI Part.

<template>
<lightning-card variant="Narrow">
<!--Spinner-->
<div if:false={isLoaded} class="slds-is-relative">
<lightning-spinner alternative-text="Loading..." variant="brand"></lightning-spinner>
</div>
<div if:true={isLoaded}>
<!-- MainBlock Here -->
<lightning-record-edit-form id="recordViewForm"
record-type-id={selectedRecordType}
object-api-name="Contact">
<lightning-messages></lightning-messages><lightning-layout multiple-rows><!-- Field Population Starts Here-->
<template if:true={allFields} for:each={allFields} for:item="field" for:index="index">
<lightning-layout-item size="6" padding="around-small" key={field}><lightning-input-field field-name={field.FieldName}></lightning-input-field>
</lightning-layout-item>
</template>
<!-- Field Population Ends Here-->
</lightning-layout><!-- submit --><lightning-button type="submit" label="Update record"></lightning-button></lightning-record-edit-form>
</div>
</lightning-card>
</template>

Now that the UI Part is done let me show you how it actually looks like.

This concludes my solutioning part. Its a half baked solution and really needs refinement for future use but achieving till this far makes me feel grateful.

The one drawback to this is the placement of fields wont be same as the in layout.

Let me know how you feel my solution and please feel free to reach me out ill try my best to help you out.😊

--

--

SoomjeetSahoo

Salesforce Dev @ Salesforce with lots of inquisitiveness to learn and grow in Salesforce and Node Domains. Currently 15x Salesforce Certified & 14x Super Badge.