> For the complete documentation index, see [llms.txt](https://docs.gridmate.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gridmate.io/advanced-guides/grid-custom-column.md).

# Grid - Custom Column

{% hint style="warning" %}
LWS must be activated in your organization to access this feature.
{% endhint %}

## Introduction

GridMate renders every column with a built-in cell type based on the field type (text, picklist, lookup, date, file...). However there is some situations where a specific rendering or a specific editing experience is required to address the business needs: a rating widget, a color picker, a progress bar, a slider...

To address this type of use cases, you can bring your own Lightning Web Component and let the grid render it in the cell, in view mode as well as in edit mode. A custom cell is a [Dynamic Formula Field](/advanced-guides/grid-dynamic-formula-field.md) whose **Type** is **Custom** and whose formula returns the `LWC()` function.

```javascript
LWC("c/ratingCellLWC", "field", "Rating__c", "max", 5)
```

* The first argument is the component to render: `c/<componentName>` for a component of your org, `gmpkg/<componentName>` for a component shipped by GridMate.
* The next arguments are name/value pairs. Each pair is passed to the component as a public property.

{% hint style="info" %}
`LWC()` is available in the formula editor under the **Other** functions. See [Javascript Formulas](/package-reference/javascript-formulas.md) for the complete syntax.
{% endhint %}

## Cell Component Interface

GridMate defines an interface which is a set of properties, events and optional methods.

### Properties

The grid passes the following properties to the component. Any name/value pair defined in the `LWC()` function is passed as an additional property.

```javascript
// The Id of the master record (record page or master record)
@api recordId;
// The row being rendered (all the fields loaded by the grid)
@api item;
// The column definition (name, label, type...)
@api column;
// true when the grid is in view mode, false when the grid is in edit mode
@api readMode;
```

{% hint style="info" %}
The same component instance is used in view mode and in edit mode. `readMode` is updated when the user switches the grid to edit mode, so the component is responsible for rendering both experiences.
{% endhint %}

### Events

```javascript
// Event to be fired when the user changes the value
// detail.value is an object of field API name => value
this.dispatchEvent(
    new CustomEvent('valuechanged', {
        detail: {
            value: { Rating__c: 4 }
        }
    })
);
```

The value of the `valuechanged` event is merged into the row. Therefore a single cell can update several fields of the record at once, e.g. `{ BillingCity: "Paris", BillingCountry: "France" }`. The fields are saved with the rest of the row when the user saves the grid.

### Methods

The following methods are optional. Implement them only when needed.

```javascript
// Called before saving the grid. Return false to block the save.
@api
reportValidity() {
    return true;
}

// Called when a value is pushed to the cell (e.g. Mass Update)
@api
setValue(newValue) {}
```

## Step by Step Implementation

### LWC Component

Our use case is to display a star rating in the grid. In view mode, the stars are displayed as text. In edit mode, the user clicks a star to set the rating. The rating is stored in a number field, whose API name is passed to the component through the `field` property.

{% tabs %}
{% tab title="ratingCellLWC.html" %}

```html
<template>
    <template lwc:if={readMode}>
        <span class="slds-truncate" title={ratingLabel}>{ratingLabel}</span>
    </template>

    <div lwc:else class="slds-grid slds-grid_vertical-align-center">
        <template for:each={stars} for:item="star">
            <lightning-button-icon
                key={star.value}
                data-value={star.value}
                variant="bare"
                size="small"
                icon-name={star.icon}
                alternative-text={star.title}
                title={star.title}
                onclick={handleStarClick}
            ></lightning-button-icon>
        </template>
    </div>
</template>
```

{% endtab %}

{% tab title="ratingCellLWC.js" %}

```javascript
import { LightningElement, api } from 'lwc';

export default class RatingCellLWC extends LightningElement {
    // Properties passed by the grid
    @api recordId;
    @api item;
    @api column;
    @api readMode;

    // Properties passed by the LWC() formula
    @api field;
    @api max = 5;

    rating = 0;

    connectedCallback() {
        this.rating = Number(this.item[this.field]) || 0;
    }

    get maxStars() {
        return Number(this.max) || 5;
    }

    get ratingLabel() {
        return (
            '★'.repeat(this.rating) +
            '☆'.repeat(Math.max(this.maxStars - this.rating, 0))
        );
    }

    get stars() {
        return Array.from({ length: this.maxStars }, (_, index) => {
            const value = index + 1;

            return {
                value,
                icon:
                    value <= this.rating
                        ? 'utility:favorite'
                        : 'utility:favorite_alt',
                title: `${value} / ${this.maxStars}`
            };
        });
    }

    @api
    reportValidity() {
        return true;
    }

    @api
    setValue(newValue) {
        this.rating = Number(newValue) || 0;
    }

    handleStarClick(event) {
        this.rating = parseInt(event.currentTarget.dataset.value, 10);

        // Notify the grid: the value is saved with the row
        this.dispatchEvent(
            new CustomEvent('valuechanged', {
                detail: {
                    value: { [this.field]: this.rating }
                }
            })
        );
    }
}
```

{% endtab %}

{% tab title="ratingCellLWC.js-meta.xml" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <isExposed>true</isExposed>
</LightningComponentBundle>
```

{% endtab %}
{% endtabs %}

### Column Configuration

Our LWC component is ready. Let's go ahead and add the custom column to the grid:

1. Open the **Config. Wiz** of the grid and go to the **Formulas** tab.
2. Set the **Type** to **Custom** and set the **Label** of the column (e.g. Rating).
3. Set the formula as below, click **Check Syntax** then **Add Formula**.

```javascript
LWC("c/ratingCellLWC", "field", "Rating__c", "max", 5)
```

4. Copy the generated **Formulas** JSON into the **Formula Columns** property of the grid in the App Builder.

{% hint style="info" %}
When **Enable Create Formula** is checked on the grid, end users can also create custom columns from the grid toolbar using **Create Formula**.
{% endhint %}

## Creating Child Records from a Cell

A custom cell is not limited to the fields of its own row: the value it pushes to the grid can also carry records of another object. The grid saves them with the row, in the same save, and reports their errors on the row. Combined with an External Id, a cell can even create the children of a row that does not exist yet: the parent and its children are created in one save.

### Child records

Add to the `valuechanged` value a key ending with `__r` (any name, e.g. `Attendees__r`) whose value is the list of child records. Each child record carries:

* `attributes.type` — the API name of the child object.
* `UID` — the `UID` of the row, so that save errors are reported on the row.
* `gm__fieldLevel: -1` — the child is saved after the row.
* The fields of the child record. To delete an existing child, send its `Id` with `attributes.deleted` set to `true`.

```javascript
this.dispatchEvent(
    new CustomEvent('valuechanged', {
        detail: {
            value: {
                Attendees__r: [
                    // New child record
                    {
                        UID: this.item.UID,
                        gm__fieldLevel: -1,
                        Event__c: this.item.Id,
                        Attendee__c: userId,
                        attributes: { type: 'Event_Attendee__c' }
                    },
                    // Existing child record to delete
                    {
                        UID: this.item.UID,
                        gm__fieldLevel: -1,
                        Id: attendee.Id,
                        attributes: { type: 'Event_Attendee__c', deleted: true }
                    }
                ]
            }
        }
    })
);
```

### Linking children to a new row

When the row is being created, it has no `Id` yet, so the child cannot reference it through the lookup field. Instead, the child references the parent through the relationship field (`Event__r`) as a nested record without `Id` carrying an External Id:

```javascript
{
    UID: this.item.UID,
    gm__fieldLevel: -1,
    Attendee__c: userId,
    Event__r: {
        attributes: { type: 'Event' },
        ExternalId__c: this.item.ExternalId__c
    },
    attributes: { type: 'Event_Attendee__c' }
}
```

The grid inserts the row first, then the children, and Salesforce resolves the parent by its External Id. The row needs a unique External Id before it is saved: stamp it through the **Default Values** property with an Apex default value provider (see [Grid - Advanced Configuration](/advanced-guides/grid-configuration.md#apex-default-value-provider)):

```json
{
    "ExternalId__c": "$APEX.ActivityExternalIdProvider()"
}
```

{% hint style="warning" %}

* The External Id field must be flagged **External ID** and **Unique**.
* Send only the External Id in the nested parent record.
  {% endhint %}
