Record Layout (LWC)

The GM - Record Layout (LWC) component is designed to display related standard or custom Salesforce objects using a configurable layout. It overcomes the limitations of the Salesforce UI API, offering a more flexible and powerful way to interact with data.

This component enables the manipulation of any Salesforce object standard or custom with dedicated support for Event and Task records.

Global Key Features

  • Multi-Column Layout: Supports layouts with more than two columns for greater flexibility and improved visual organization.

  • Dynamic Forms: Dynamically display fields based on the values of other fields, enabling intelligent and responsive form behavior.

  • Advanced Error Handling: Implements Salesforce-like record and field-level error handling to ensure a consistent user experience.

  • Conditional Coloring: Supports custom coloring rules to enhance readability and highlight key data.

Edit Features

  • Edit and Form Modes: Easily switch between read-only and editable modes to accommodate different interaction needs.

  • Delegated Save: Provides delegated save functionality for streamlined data handling and custom logic control.

  • Default Values: Allows setting default field values for faster and more accurate record creation.

Fully supports pre-save and post-save hooks for custom logic execution during the save process

Enhanced Record Layout

Multi-Column Layout

GridMate bypasses Salesforce’s standard 2 columns layout limitation, allowing for more flexible and customizable record layouts.

Below the the Multi-Column Layout Json configuration.

{
    "density": "comfy",
    "sections": [
        {
            "name": "accountInformation",
            "label": "Account Information",
            "active": true,
            "cols": 3,
            "rows": [
                [
                    {
                        "apiName": "Name"
                    },
                    {
                        "apiName": "AccountSource"
                    },
                    {
                        "apiName": "Industry"
                    }
                ],
                [
                    {
                        "apiName": "Phone"
                    },
                    {
                        "apiName": "Fax"
                    },
                    {
                        "apiName": "Website"
                    }
                ]
            ]
        },
        {
            "name": "addressInformation",
            "label": "Address Information",
            "active": true,
            "cols": 2,
            "rows": [
                [
                    {
                        "apiName": "BillingStreet"
                    },
                    {
                        "apiName": "ShippingStreet"
                    }
                ],
                [
                    {
                        "apiName": "BillingCity"
                    },
                    {
                        "apiName": "ShippingCity"
                    }
                ],
                [
                    {
                        "apiName": "BillingPostalCode"
                    },
                    {
                        "apiName": "ShippingPostalCode"
                    }
                ]
            ]
        }
    ]
}

Dynamic Forms

Dynamically display fields or sections based on the values of other fields.

Below is the JSON configuration used for this example:

{
    "apiName": "Website",
    "visibility": {
        "Industry": {
            "operator": "=",
            "value": "Media"
        }
    }
}

Advanced Error Handling

We can handle both field-level and record-level errors. In this example, we’ve created two validation rules:

  • The first rule triggers an error message at the field level.

  • The second rule displays the error at the record layout level.

Below is an example of how it will be displayed on the UI:

  • The first validation rule shows an error message directly on the field where the issue occurs.

  • The second validation rule displays the error at the record layout level, indicating a broader record-related issue.

Conditional Coloring

Below is an example of Conditional Coloring: When the Account Source is set to Partner, the Account Site field is automatically highlighted in green to visually indicate the condition, and this behavior works in both Edit and Read modes.

Below is the JSON configuration used for this example:

{
    "apiName": "Site",
    "coloring": [
        {
            "color": "#a6f79e",
            "exp": {
                "AccountSource": {
                    "operator": "=",
                    "value": "Partner"
                }
            }
        }
    ]
}

Edit/Form Mode

To enable Edit Form Mode, the component must be wrapped inside an Aura wrapper. Below is the configuration used for this setup.

When using Edit Mode, make sure to set the defaultValues property. Leave it empty only if you don’t have any default information to display Otherwise, the component will not appear.

Component Managed Save

Allow the component to handle the save process and error handling automatically. You can also redirect or customize any post-save actions inside the handleSave method

<aura:component implements="force:hasSObjectName,
                            force:appHostable,
                            flexipage:availableForAllPageTypes,
                            flexipage:availableForRecordHome,
                            force:hasRecordId"
                access="global">
                
    <aura:attribute name="sObjectName" type="String" />
    <aura:attribute name="recordLayoutJSON" type="String" />
    <aura:attribute name="defaultValues" type="Object" />
    <aura:attribute name="isReady" type="Boolean" default="false" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <aura:if isTrue="{!v.isReady}">
        <gmpkg:RecordLayoutLWCComponent aura:id="recordLayout"
                                        sObjectName="{!v.sObjectName}"
                                        editMode="true"
                                       
                                        recordId="{!v.recordId}"
                                        recordIdField="recordId"
                                        targetObjName="Account"
                                        
                                        recordLayout="{!v.recordLayoutJSON}"
                                        
                                        defaultValues="{!v.defaultValues}" 
                                        
                                        onsuccess="{!c.handleSuccess}"
                                        />
    </aura:if>
</aura:component>

Delegated Save

If you prefer complete control over the save logic, enable delegatedSave. In this mode, the new record or changes are exposed in the handleSubmit method, and the database save operation must be explicitly handled by the developer.

<aura:component implements="force:hasSObjectName,
                            force:appHostable,
                            flexipage:availableForAllPageTypes,
                            flexipage:availableForRecordHome,
                            force:hasRecordId"
                access="global">
                
    <aura:attribute name="sObjectName" type="String" />
    <aura:attribute name="recordLayoutJSON" type="String" />
    <aura:attribute name="defaultValues" type="Object" />
    <aura:attribute name="isReady" type="Boolean" default="false" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <aura:if isTrue="{!v.isReady}">
        <gmpkg:RecordLayoutLWCComponent aura:id="recordLayout"
                                        sObjectName="{!v.sObjectName}"
                                        editMode="true"
                                        
                                        recordId="{!v.recordId}"
                                        recordIdField="recordId"
                                        targetObjName="Account" 
                                        
                                        recordLayout="{!v.recordLayoutJSON}"
                                        
                                        defaultValues="{!v.defaultValues}"
                                        
                                        delegatedSave="true"
                                        onsubmit="{!c.handleSubmit}"
                                        />
    </aura:if>
</aura:component>

When both recordId and recordIdField are provided, the component can load and edit an existing record: - recordId : identifies which record to load. - recordIdField : specifies where the record ID is stored in the layout.

If NOT, there’s no record context. So the layout opens as a blank form with default or empty fields, ready for new record creation.

No action bar

It allows you to add a footer section with Save and other custom buttons that aren’t part of the default layout action bar.

The Save button calls recordLayout.saveRecord() to save the current record.

<aura:component implements="force:hasSObjectName,
                            force:appHostable,
                            flexipage:availableForAllPageTypes,
                            flexipage:availableForRecordHome,
                            force:hasRecordId"
                access="global">

    <!-- Attributes -->
    <aura:attribute name="sObjectName" type="String" />
    <aura:attribute name="recordLayoutJSON" type="String" />
    <aura:attribute name="defaultValues" type="Object" />
    <aura:attribute name="isReady" type="Boolean" default="false" />

    <!-- Init Handler -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <!-- Main Body -->
    <aura:if isTrue="{!v.isReady}">
        <div class="slds-card slds-p-around_medium">

            <!-- Record Layout -->
            <gmpkg:RecordLayoutLWCComponent aura:id="recordLayout"
                                            sObjectName="{!v.sObjectName}"
                                            editMode="true"
                                            noActionBar="true"
                                            targetObjName="Account"
                                            recordLayout="{!v.recordLayoutJSON}"
                                            defaultValues="{!v.defaultValues}"
                                            onsuccess="{!c.handleSuccess}"
                                            />

            <!-- Footer with Buttons -->
            <div class="slds-text-align_center slds-m-top_medium">
                <lightning:button label="Cancel"
                                  variant="neutral"
                                  onclick="{!c.handleCancel}" />

                <lightning:button label="Save"
                                  variant="brand"
                                  class="slds-m-left_small"
                                  onclick="{!c.handleSave}" />
            </div>

        </div>
    </aura:if>
</aura:component>

Record Layout Description

1. Overall Structure

The JSON defines a record layout configuration for a UI component.

At the top level:

  • density → controls field spacing ("comfy", "compact").

  • sections → an array of section objects, each defining its own layout and behavior.

2. Section object properties

Each section in "sections": [ … ] has these key properties:

Property
Description

name

Internal unique identifier for the section (used in logic).

{
  "name": "accountInformation"
}

label

The display title of the section, shown to users.

{
  "label": "Account Information"
}

active

A flag (true/false or special value like "always") that controls if the section is enabled. If set to "always" it means the section is always active.

{
  "name": "contactInformation",
  "label": "Contact Information",
  "active": "always",
  "cols": 2,
  "rows": [
    [
      { "apiName": "Email" },
      { "apiName": "Phone" }
    ]
  ]
}

cols

Number of columns to display in that section. This controls layout/grid: how many fields (or blocks) appear per row.

{
  "name": "accountInformation",
  "label": "Account Information",
  "cols": 3,
  "rows": [
    [
      { "apiName": "Name" },
      { "apiName": "Phone" },
      { "apiName": "Website" }
    ]
  ]
}

visibility

A visibility condition (JSON object) that determines whether the entire section is visible or hidden based on field values of the record. This follows the same rule‐structure as the “readOnly” conditions (operator, value).

{
  "name": "addressInformation",
  "label": "Address Information",
  "active": true,
  "visibility": {
    "BillingCountry": { "operator": "=", "value": "USA" }
  },
  "cols": 2,
  "rows": [
    [
      { "apiName": "BillingStreet" },
      { "apiName": "ShippingStreet" }
    ]
  ]
}

readOnly

A read‐only condition for the whole section. If this evaluates true, all fields in the section become non‐editable.

{
  "name": "accountInformation",
  "label": "Account Information",
  "active": true,
  "readOnly": {
    "Industry": { "operator": "=", "value": "Banking" }
  },
  "cols": 3,
  "rows": [
    [
      { "apiName": "Name" },
      { "apiName": "Industry" },
      { "apiName": "Phone" }
    ]
  ]
}

rows

An array of rows. Each row is itself an array of “cells” (either field definitions or emptyBlock definitions) that fill up to cols columns. Fields can span columns using colSize.

{
  "name": "accountInformation",
  "label": "Account Information",
  "cols": 3,
  "rows": [
    [
      { "apiName": "Name" },
      { "apiName": "Phone" },
      { "apiName": "Website" }
    ],
    [
      { "apiName": "Description", "colSize": 2 },
      { "emptyBlock": true }
    ]
  ]
}

3. Field (cell) object properties

Inside rows, each cell can be:

  • A field object with properties, or

  • An empty block object: { "emptyBlock": true } (used for layout alignment).

For field objects, common properties include:

Property
Description

apiName

The API name of the field in the data model that will be displayed.

{
  "apiName": "Name"
}

colSize

Optional. Defines how many columns wide the field spans (useful when a field should cover more than a single column).

{
  "apiName": "Description",
  "colSize": 2
}

noWhitespace

Optional boolean. If true, removes extra whitespace/margins around the field component.

{
  "apiName": "Industry",
  "noWhitespace": true
}

readOnly

A JSON condition (like visibility) that determines if the field is editable or not. Even if the section is editable, this field can individually be read‐only.

{
  "apiName": "AccountSource",
  "readOnly": {
    "AccountSource": { "operator": "in", "value": ["Web", "Other"] }
  }
}

visibility

A JSON condition that determines whether the field is shown at all.

{
  "apiName": "Industry",
  "visibility": {
    "BillingCountry": { "operator": "!=", "value": "USA" }
  }
}

coloring

An array of coloring rules: each rule has a color value and an exp object that defines a condition under which that color is applied to the field. This allows conditional highlighting.

{
  "apiName": "Status",
  "coloring": [
    {
      "color": "#a6f79e",
      "exp": { "Status": { "operator": "=", "value": "Active" } }
    }
  ]
}

emptyBlock

If the object is { "emptyBlock": true }, it means “insert an empty slot here” for layout purposes (no field displayed).

[
  { "apiName": "Description", "colSize": 2 },
  { "emptyBlock": true }
]

autocomplete

Provides a City field with an autocomplete dropdown, suggesting values like Paris, London, New York, and Rome as the user types.

{
  "apiName": "City",
  "autocomplete": ["Paris", "London", "New York", "Rome"]
}

Examples

Advanced Layout (conditional visibility, read-Only, and autocomplete)

Section 1 : Account Information

  • Active: always.

  • Read-only when: Industry = Banking.

  • 3-column grid layout.

4 rows :

  1. Name, AccountSource, Industry

    • AccountSource: read-only if “Web” or “Other”; turns green (#a6f79e) when “Web”.

    • Industry: visible only when BillingCountry != USA and with no extra whitespace.

  2. Phone, Fax, and one conditional emptyBlock that appears only when BillingCountry = USA.

  3. Website, Description (spanning 2 columns).

  4. BillingCity field uses autocomplete with static options: “Paris”, “New York”, “London”, “Rome”.


Section 2 : Address Information

  • Visible when: BillingCountry = USA.

  • Read-only when: BillingCity = New York.

  • 2-column layout.

3 rows :

  1. BillingStreet, ShippingStreet.

  2. BillingCity, ShippingCity.

  3. BillingPostalCode, ShippingPostalCode.


Section 3 : SSN Information

  • Visible when: Account.BillingCountry = USA.

  • Read-only when: Account.Type != Customer.

  • 1-column layout.

  • Displays a single field SSN__c.

{
            "density": "comfy",
            "sections": [
                {
                    "name": "accountInformation",
                    "label": "Account Information",
                    "active": "always",
                    "cols": 3,
                    "readOnly": {
                        "Industry": { "operator": "=", "value": "Banking" }
                    },
                    "rows": [
                        [
                            { "apiName": "Name" },
                            {
                                "apiName": "AccountSource",
                                "readOnly": {
                                    "AccountSource": { "operator": "in", "value": ["Web", "Other"] }
                                },
                                "coloring": [
                                    {
                                        "color": "#a6f79e",
                                        "exp": { "AccountSource": { "operator": "=", "value": "Web" } }
                                    }
                                ]
                            },
                            {
                                "apiName": "Industry",
                                "noWhitespace": true,
                                "visibility": {
                                    "BillingCountry": { "operator": "!=", "value": "USA" }
                                }
                            }
                        ],
                        [
                            {
                                "emptyBlock": true,
                                "visibility": {
                                    "BillingCountry": { "operator": "=", "value": "USA" }
                                }
                            },
                            { "apiName": "Phone" },
                            { "apiName": "Fax" }
                        ],
                        [
                            { "apiName": "Website" },
                            { "apiName": "Description", "colSize": 2 }
                        ],
                        [
                            {
                                "apiName": "BillingCity",
                                "autocomplete": ["Paris", "New York", "London", "Rome"]
                            },
                            { "emptyBlock": true },
                            { "emptyBlock": true }
                        ]
                    ]
                },
                {
                    "name": "addressInformation",
                    "label": "Address Information",
                    "active": true,
                    "cols": 2,
                    "visibility": {
                        "BillingCountry": { "operator": "=", "value": "USA" }
                    },
                    "readOnly": {
                        "BillingCity": { "operator": "=", "value": "New York" }
                    },
                    "rows": [
                        [
                            { "apiName": "BillingStreet" },
                            { "apiName": "ShippingStreet" }
                        ],
                        [
                            { "apiName": "BillingCity" },
                            { "apiName": "ShippingCity" }
                        ],
                        [
                            { "apiName": "BillingPostalCode" },
                            { "apiName": "ShippingPostalCode" }
                        ]
                    ]
                }
            ]
        }
Minimal Contact Info (2 columns, autocomplete, emptyBlock)
{
  "density": "comfy",
  "sections": [
    {
      "name": "contactInfo",
      "label": "Contact Information",
      "active": true,
      "cols": 2,
      "rows": [
        [
          { "apiName": "FirstName" },
          { "apiName": "LastName" }
        ],
        [
          { "apiName": "Email" },
          { "apiName": "Phone" }
        ],
        [
          { "apiName": "MailingCity", "autocomplete": ["Paris", "London", "New York", "Rome"] },
          { "emptyBlock": true }
        ]
      ]
    }
  ]
}
Opportunity Summary (3 columns, field visibility, coloring)
{
  "density": "comfy",
  "sections": [
    {
      "name": "opptySummary",
      "label": "Opportunity Summary",
      "active": "always",
      "cols": 3,
      "rows": [
        [
          { "apiName": "Name" },
          {
            "apiName": "StageName",
            "coloring": [
              { "color": "#a6f79e", "exp": { "StageName": { "operator": "in", "value": ["Prospecting", "Qualification"] } } }
            ]
          },
          { "apiName": "CloseDate" }
        ],
        [
          { "apiName": "Amount" },
          {
            "apiName": "Competitor__c",
            "visibility": { "Type": { "operator": "=", "value": "New Business" } }
          },
          { "apiName": "OwnerId" }
        ],
        [
          { "apiName": "Description", "colSize": 2 },
          { "emptyBlock": true }
        ]
      ]
    }
  ]
}
Case Details (section readOnly + field visibility)
{
  "density": "comfy",
  "sections": [
    {
      "name": "caseDetails",
      "label": "Case Details",
      "active": true,
      "cols": 2,
      "readOnly": {
        "Status": { "operator": "in", "value": ["Closed", "Resolved"] }
      },
      "rows": [
        [
          { "apiName": "Subject" },
          { "apiName": "Priority" }
        ],
        [
          {
            "apiName": "SLA__c",
            "coloring": [
              { "color": "#f7b267", "exp": { "SLA__c": { "operator": "=", "value": "At Risk" } } }
            ]
          },
          {
            "apiName": "Escalated__c",
            "visibility": { "Priority": { "operator": "=", "value": "High" } }
          }
        ],
        [
          { "apiName": "Description", "colSize": 2 }
        ]
      ]
    }
  ]
}

Last updated

Was this helpful?