Exercise 7Form Builder Component

Form Builder Component

This component allows an App Builder user to create a form declaratively. After creating the component, place it on a Property page in App Builder. You can then use the Design Attributes to populate the form. Start with assigning Property__c as the object, or comment/uncomment the property for ‘object’ in the component’s metadata to use a dynamic picklist to select the object. Use this in the RecordId field to load the current record into the form. Select a layoutType, or a comma-seperated list of field names.

formBuilder.html
<template>
    <lightning-card icon-name={icon}
                    title={title}>
        <div class="slds-p-left_large slds-p-right_medium">
            <template if:true={object}>
                <template if:true={message}>
                    <h3>{message}</h3>
                </template>
                <lightning-record-form object-api-name={object}
                                       mode={mode}
                                       columns={columns}></lightning-record-form>
            </template>
            <template if:false={object}>
                <h3>{message}</h3>
            </template>
        </div>
    </lightning-card>
</template>
formBuilder.js
import { LightningElement, api, track } from 'lwc';

export default class formBuilder extends LightningElement {
    @api recordId;
    @api objectApiName;
    @api fieldsToDisplay = '';
    @api title;
    @api icon;
    @api mode;
    @api object;
    @api record;
    @api layout;
    @api columns;
    @track init;
    @track message;
    @track fields = [];

    connectedCallback() {  
    }

    renderedCallback() {
        if (!this.object) {
            this.message = "Please provide an object to begin.";
            return;
        }

        let form = this.template.querySelector('lightning-record-form');

        if (this.layout === 'None' && this.fieldsToDisplay === '') {
            this.message = "Please select a layout and/or provide a list of fields."
        }

        if (this.layout !== 'None') {
            form.layoutType = this.layout;
        }

        if (this.fieldsToDisplay !== '') {
            this.fields = this.fieldsToDisplay.split(",");
            form.fields = this.fields;
        }

        if (this.record === 'this') {
            form.recordId = this.recordId;
        } else if (this.record !== '') {
            form.recordId = this.record;
        }

    }
}

formBuilder.js-meta.xml
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="formBuilder">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Form Builder</masterLabel>
    <targets>
      <target>lightning__AppPage</target>
      <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
      <targetConfig targets="lightning__RecordPage">
      <property name="object" label="Object" type="String" /> 
      <!-- <property name="object" label="Object" type="String" datasource="apex://DescribeObjects" /> -->
      <property name="record" label="Record Id" type="String" />
      <property name="title" label="Card Title" type="String" />
      <property name="icon" label="Card Icon" type="String" />
      <property name="fieldsToDisplay" label="Fields to Display" type="String" description="A comma-seperated list of field names. If you have a selected a layoutType, use this to add fields not on the standard layout." />
      <property name="layout" label="Layout Type" type="String" datasource="Full,Compact,None" default="None" />
      <property name="columns" label="Number of Columns" type="String" default="1" />
      <property name="mode" label="Form Mode" type="String" datasource="view,edit,readonly" default="view" />
      </targetConfig>
  </targetConfigs>
</LightningComponentBundle>
formBuilder.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
	<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
		<path d="M120,108 C120,114.6 114.6,120 108,120 L12,120 C5.4,120 0,114.6 0,108 L0,12 C0,5.4 5.4,0 12,0 L108,0 C114.6,0 120,5.4 120,12 L120,108 L120,108 Z" id="Shape" fill="#49D5D9"/>
		<path d="m73.9 65.9c-0.2 3.4-0.6 7.1-1.2 10.6-0.2 1.1-1.2 2.1-2.3 2.2-6.8 0.9-13.6 1.3-20.4 1.3-6.7 0-13.5-0.4-20.2-1.3-1.1-0.1-2.1-1.2-2.3-2.2-1-5.6-1.5-11.3-1.5-16.9 0-5.7 0.5-11.4 1.4-16.9 0.2-1.1 1.2-2.1 2.3-2.3 4.1-0.5 8.2-0.8 12.2-1 0 0 3.3-0.2 3.1-3.2-0.2-2.8-5-4.6-5-9.4 0-3.8 3.8-6.8 9.9-6.8 6.1 0 9.9 3.1 9.9 6.8 0 4.7-4.7 6.6-4.9 9.4-0.2 3.1 3 3.2 3 3.2 4.1 0.2 8.3 0.5 12.4 1 1.1 0.2 2.1 1.2 2.3 2.3 0.7 3.9 1.1 7.4 1.3 11.2 0.1 1.1-0.9 2-2.1 2-0.4 0-0.7-0.1-1.1-0.1-1.2 0-2.9-0.7-3.7-1.6 0 0-2.7-2.7-5.5-2.7-4.6-0.1-8.2 4.1-8.2 8.5s3.5 8.6 8.1 8.5c2.8-0.1 5.5-2.9 5.5-2.9 0.9-0.8 2.5-1.6 3.7-1.6 0.4-0.1 0.7-0.1 1.1-0.1 1.4 0.1 2.3 1 2.2 2z" id="Path-1" fill="#FFFFFF"/>
	</g>
</svg>
DescribeObjects.cls
// Thanks to my colleague, Rodrigo Reboucas, for the help in creating this class.
// All code is provided "as is", with no expectation of support from Salesforce.

global with sharing class DescribeObjects extends VisualEditor.DynamicPickList {

    global override VisualEditor.DataRow getDefaultValue(){
        VisualEditor.DataRow defaultValue = new VisualEditor.DataRow('Property__c', 'Property__c');
        return defaultValue;
    }
    global override VisualEditor.DynamicPickListRows getValues() {
        VisualEditor.DynamicPickListRows  myValues = new VisualEditor.DynamicPickListRows();
        
        map<string, SObjectType> objs = schema.getGlobalDescribe();
        List<String> lstObjNames = new List<String>();
        for(string key: objs.keySet()){
            String val = objs.get(key).getDescribe().getName();
            Schema.DescribeSObjectResult objResult = objs.get(key).getDescribe();
            String objLabel =  objResult.getLabel();
            Boolean isSearchable = objResult.isSearchable();
            Boolean isQueryable = objResult.isQueryable();
            System.Debug('objLabel: ' + objLabel +  '  - value: ' + val + 'isSearchable: ' + isSearchable);
            if (isQueryable && isSearchable && !val.containsignorecase('history') && !val.containsignorecase('tag')&& !val.containsignorecase('share') && !val.containsignorecase('feed')) {
                lstObjNames.add(val);
            }
        }

        if (lstObjNames.size() > 0)
        {
            lstObjNames.sort();
            for (string name: lstObjNames)
            {
                VisualEditor.DataRow value = new VisualEditor.DataRow(name,name);
                myValues.addRow(value);
            }
        }
        return myValues;
    }
}