Create a Custom Data Type for lightning-tree-grid

The lightning-tree-grid component uses lightning-datatable to format data based on the type you specify for the column.

Before you create your own data type, check the standard data types to see if one meets your requirements. You can use type attributes to customize the output for many types. The standard data types are:

  • action
  • boolean
  • button
  • button-icon
  • currency
  • date
  • date-local
  • email
  • location
  • number
  • percent
  • phone
  • text (default)
  • url

For more information on standard data types and their type attributes, see the lightning-tree-grid reference documentation.

This documentation uses the term lightning-tree-grid component and tree grid interchangeably.

Note

How to Define Custom Data Types 

The lightning-tree-grid component supports these approaches for defining custom types.

  • Extend the LightningTreeGrid class. If your use case is simple, create a new tree grid component that defines the custom type. A simple use case means the custom type can use a standard cell layout supplied by the tree grid, and you don’t require use of dynamic custom types.
  • Use a slot. If you want to use dynamic custom types, pass the custom type into the lightning-tree-grid component in a slot. See Pass in Custom Data Types Using a Slot.

Define Your Custom Type by Extending LightningTreeGrid 

Create your own data type to implement a custom cell, such as a custom text or number display. You can also apply a custom class for each row on your custom data type.

To define and use a custom data type, extend the LightningTreeGrid class of the lightning-tree-grid component in a new component.

You can extend from LightningTreeGrid only to create a tree grid with custom data types. Unless stated otherwise, extending any class besides LightningElement to create a Lightning web component isn’t supported.

Note

Create a Lightning web component and define your type in an HTML template in the component folder. The template can contain the complete UI for a simple data type that doesn’t require JavaScript. The template can also embed a component that you define in another folder. For example, use a separate component when you want to include logic that determines what to display.

The UI that you include in the template can be whatever suits your use case and usability requirements.

Let’s look at the folder structure for the component customDatatypeTreegrid, which defines two custom types.

1customDatatypeTreegrid
2   ├──customName.html
3   ├──customNumber.html
4   ├──customDatatypeTreegrid.js
5   └──customDatatypeTreegrid.js-meta.xml

In your JavaScript file customDatatypeTreegrid.js, extend the LightningTreegrid class and specify your type’s name and template file. This example creates a custom name type and custom number type using the customName.html and customNumber.html templates.

The names of the type and template don’t have to match. The example uses customName for both the type name and the template file name. We recommend that you import the templates with different names to make it clear where you specify the type name and template name.

Note

1// customDatatypeTreegrid.js
2import LightningTreeGrid from "lightning/treeGrid";
3import customNameTemplate from "./customName.html";
4import customNumberTemplate from "./customNumber.html";
5
6export default class CustomDatatypeTreegrid extends LightningTreeGrid {
7  static customTypes = {
8    customName: {
9      template: customNameTemplate,
10      typeAttributes: ["industryName"],
11      standardCellLayout: true,
12    },
13    customNumber: {
14      template: customNumberTemplate,
15      typeAttributes: ["min"],
16    },
17  };
18}

Pass in the following properties to the customTypes object.

Custom Type PropertyTypeDescription
templatestringThe name of your type’s imported HTML template.
typeAttributesarrayThe comma-separated list of attributes to pass to the custom data template. Access your data using the typeAttributes.attributeName syntax.
standardCellLayoutbooleanSpecifies whether the standard layout is used. The default is false. The standard layout is used by all standard data types. The default layout for custom data types is the bare layout. You can use the standardCellLayout to style cells for your custom data type to make them look similar to the standard data types. The standardCellLayout also supports accessibility and keyboard navigation.

The first data column supports custom data types with standardCellLayout set to true only.

Note

Create a Custom Data Template 

In your custom data template customName.html, add the markup for your data type. This example creates a custom type that renders a text label using a lightning-badge component.

1<!-- customName.html -->
2<template>
3    <template lwc:if={typeAttributes.industryName}>
4        <lightning-badge label={typeAttributes.industryName} icon-name="standard:account">
5        </lightning-badge>
6    </template>
7</template>

Using the lwc:if conditional directive makes sure that the badge is displayed only when the record includes an industry name.

Create a Nested Custom Data Template 

The customName example is a simple data type that’s expressed with only HTML. Let’s take a look at the more complex customNumber example, which composes a child component from a separate component bundle.

1<!-- customNumber.html -->
2<template>
3    <c-custom-datatype-number value={value}></c-custom-datatype-number>
4</template>

The child component contains a lightning-formatted-number base component that displays in red or green text color depending on its value. An icon is displayed next to the number based on its value.

1<!-- customDataTypeNumber.html -->
2<template>
3    <div class={computedClass}>
4        <lightning-formatted-number format-style="currency" value={value}>
5        </lightning-formatted-number>
6        <template lwc:if={value}>
7            <lightning-icon class="slds-p-horizontal_xx-small" icon-name={computedIcon} size="xx-small"
8                variant={iconVariant}>
9        </lightning-icon>
10    </template>
11    </div>
12</template>

The JavaScript file contains the customization for the text colors, icon names, and icon variants.

1// customDataTypeNumber.js
2import { LightningElement, api } from "lwc";
3
4export default class CustomDatatypeNumber extends LightningElement {
5  @api value;
6
7  get computedClass() {
8    return this.value > 100000000 ? "slds-text-color_success" : "slds-text-color_error";
9  }
10
11  get computedIcon() {
12    return this.value > 100000000 ? "utility:arrowup" : "utility:arrowdown";
13  }
14
15  get iconVariant() {
16    return this.value > 100000000 ? "success" : "error";
17  }
18}

Example: Implement Your Treegrid with the Custom Types 

Let’s implement a lightning-tree-grid component that uses the custom data types. The first column displays the account name using the standard text type. The second column uses the customName data type and the third column uses the customNumber data type. The nested contact names and email use standard data types. The last column is a button-icon standard type that displays a record edit modal when clicked.

Record data displayed in a treegrid with custom types

Display 10 account records with associated contacts in your component using an Apex controller.

1// AccountController.cls
2public with sharing class AccountController {
3  @AuraEnabled(Cacheable=true)
4    public static list<Account> getAccountsWithContacts(){
5        return [SELECT Id, Name, Industry, AnnualRevenue, (SELECT Id, FirstName, LastName, Email FROM Contacts) FROM Account WITH USER_MODE LIMIT 10];
6    }
7}

To implement the treegrid with the custom data types, create a wrapper component to contain your extended treegrid component. Define the columns and fetch data. Here we use accountsTreegrid as the wrapper component. It extends NavigationMixin(LightningElement) so we can use the lightning/navigation module to navigate to a record edit modal.

1// accountsTreegrid.js
2import { LightningElement, track, wire } from "lwc";
3import { NavigationMixin } from "lightning/navigation";
4import getAccountsWithContacts from "@salesforce/apex/AccountController.getAccountsWithContacts";
5
6export default class AccountsTreegrid extends NavigationMixin(LightningElement) {
7  accounts;
8  error;
9
10  @wire(getAccountsWithContacts)
11  wiredAccounts({ error, data }) {
12    if (data) {
13      this.accounts = data.map((account) => ({
14        ...account,
15        _children: account.Contacts,
16      }));
17    } else if (error) {
18      this.error = error;
19      this.accounts = undefined;
20    }
21  }
22  constructor() {
23    super();
24    this.columns = [
25      {
26        type: "text",
27        fieldName: "Name",
28        label: "Account Name",
29      },
30      {
31        type: "customName",
32        label: "Industry",
33        typeAttributes: {
34          industryName: { fieldName: "Industry" },
35        },
36      },
37      {
38        type: "customNumber",
39        fieldName: "AnnualRevenue",
40        label: "Annual Revenue",
41      },
42      {
43        type: "text",
44        fieldName: "FirstName",
45        label: "First Name",
46      },
47      {
48        type: "text",
49        fieldName: "LastName",
50        label: "Last Name",
51      },
52      {
53        type: "email",
54        fieldName: "Email",
55        label: "Contact Email",
56      },
57
58      {
59        type: "button-icon",
60        typeAttributes: { iconName: "utility:edit", name: "edit", size: "x-small" },
61      },
62    ];
63  }
64
65  handleRowAction(event) {
66    if (event.detail.action.name === "edit") {
67      this[NavigationMixin.Navigate]({
68        type: "standard__recordPage",
69        attributes: {
70          recordId: event.detail.row.Id,
71          objectApiName: "Account",
72          actionName: "edit",
73        },
74      });
75    }
76  }
77}

The wrapper component uses data.map to create a new array based on the original data retrieved from the Apex controller. For each account in data, it creates a new object using the spread operator ... to copy all existing properties. It then adds the _children property with the value of account.Contacts. The resulting array is directly assigned to this.accounts.

Finally, assign accounts on the data attribute of the lightning-tree-grid component.

1<!-- accountsTreegrid.html -->
2<template>
3  <lightning-card title="Treegrid with Custom Data Types" icon-name="utility:table">
4    <c-custom-datatype-treegrid
5      key-field="Id"
6      data="{accounts}"
7      columns="{columns}"
8      onrowaction="{handleRowAction}"
9    >
10    </c-custom-datatype-treegrid>
11  </lightning-card>
12</template>

The handleRowAction event handler displays a record edit modal using the lightning/navigation module.

Example: Implement a Treegrid with a Dynamic Custom Data Type 

Let’s implement a lightning-tree-grid component that loads a Ratings column using a custom data type. The example adds several buttons that you can click to expand or collapse nested rows. It also adds a Ratings button that you can click to display the Ratings column.

Treegrid with a dynamic custom data type

Create your custom data type and add the custom data type component in the same folder. For example, the customProvider.html file contains an empty <template> tag and the customRating.html file contains the UI for the account rating.

1customProvider
2   ├──customProvider.html
3   ├──customProvider.js
4   ├──customProvider.js-meta.xml
5   └──customRating.html

The customProvider.js file imports and defines the custom data type within the same folder. It extends LightningElement instead of LightningTreeGrid, and it returns the custom data type definition via the public getDataTypes() method.

1// customProvider.js
2import { LightningElement, api } from "lwc";
3import customRatingTemplate from "./customRating.html";
4
5export default class CustomProvider extends LightningElement {
6  @api
7  getDataTypes() {
8    return {
9      customRating: {
10        template: customRatingTemplate,
11        standardCellLayout: true,
12        typeAttributes: ["optionValue"],
13      },
14      // Other custom types here
15    };
16  }
17}

The customRating component composes a child component that renders the corresponding icon for the account rating. It uses the lwc:if conditional directive to render the child component only if the account has a rating value passed into the optionValue property.

1<!-- customRating.html -->
2<template>
3    <template lwc:if={typeAttributes.optionValue}>
4        <c-custom-datatype-rating option-value={typeAttributes.optionValue}></c-custom-datatype-rating>  
5    </template>
6</template>

The option-value attribute passes the data to the customDatatypeRating child component.

1customDatatypeRating
2   ├──customDatatypeRating.html
3   ├──customDatatypeRating.js
4   └──customDatatypeRating.js-meta.xml

This component receives data from the parent component through the optionValue property.

1<!-- customDatatypeRating.html -->
2<template>
3    <template lwc:if={optionValue}>
4        <lightning-dynamic-icon 
5          type="trend" 
6          option={option}>
7    </lightning-dynamic-icon>  
8  </template>
9</template>

In the JavaScript file, the option getter function calculates the value of the option property. If the account rating that’s returned by optionValue is Hot, the lightning-dynamic-icon base component sets the option property to up, which displays the up trending icon. If the account rating that’s returned by optionValue is Warm, the component displays the neutral trending icon. Otherwise, the component displays the down trending icon for Cold and unknown ratings.

1import { LightningElement, api } from "lwc";
2
3export default class CustomDatatypeRating extends LightningElement {
4  @api optionValue;
5
6  get option() {
7    return this.optionValue === "Hot" ? "up" : this.optionValue === "Warm" ? "neutral" : "down";
8  }
9}

Finally, create your lightning-tree-grid component wrapper.

1accountsTreegridDynamic
2   ├──accountsTreegridDynamic.html
3   ├──accountsTreegridDynamic.js
4   └──accountsTreegridDynamic.js-meta.xml

Pass in the custom data type using the customdatatypes slot.

1<!-- accountsTreegridDynamic.html -->
2<template>
3  <lightning-card title="Treegrid Dynamic Custom Data Types" icon-name="utility:table">
4    <lightning-button-group slot="actions">
5      <lightning-button-icon icon-name="utility:expand_all" alternative-text="Expand all" onclick={handleExpandAll}></lightning-button-icon>
6      <lightning-button-icon icon-name="utility:collapse_all" alternative-text="Collapse all" onclick={handleCollapseAll}></lightning-button-icon>
7      <lightning-button-icon icon-name="utility:trending" alternative-text="Show ratings" onclick={handleCustomRating} disabled={buttonDisabled}></lightning-button-icon>
8  </lightning-button-group>
9    <lightning-tree-grid
10      lwc:ref="treegrid"
11      columns={columns}
12      data={accounts}
13      key-field="Id"
14      onrowaction={handleRowAction}
15    >
16      <c-custom-provider slot="customdatatypes"></c-custom-provider>
17    </lightning-tree-grid>
18  </lightning-card>
19</template>

Define your column data and pass in the customRating custom data type to the Rating column.

1import { LightningElement, track, wire } from "lwc";
2import getAccountsWithContacts from "@salesforce/apex/AccountController.getAccountsWithContacts";
3
4const COLS = [
5  {
6    type: "text",
7    fieldName: "Name",
8    label: "Account Name",
9  },
10  {
11    type: "text",
12    fieldName: "FirstName",
13    label: "First Name",
14  },
15  {
16    type: "text",
17    fieldName: "LastName",
18    label: "Last Name",
19  },
20  {
21    type: "email",
22    fieldName: "Email",
23    label: "Contact Email",
24  },
25  {
26    type: "customRating",
27    label: "Rating",
28    typeAttributes: {
29      optionValue: { fieldName: "Rating" },
30    },
31  },
32];
33
34export default class AccountsTreegridDynamic extends LightningElement {
35  accounts;
36  error;
37
38  showCustomTypes = false;
39  buttonDisabled = false;
40
41  @wire(getAccountsWithContacts)
42  wiredAccounts({ error, data }) {
43    if (data) {
44      this.accounts = data.map((account) => ({
45        ...account,
46        _children: account.Contacts,
47      }));
48    } else if (error) {
49      this.error = error;
50      this.accounts = undefined;
51    }
52  }
53  constructor() {
54    super();
55    this.columns = [...COLS].filter((col) => col.type != "customRating");
56  }
57
58  handleExpandAll() {
59    this.refs.treegrid.expandAll();
60  }
61
62  handleCollapseAll() {
63    this.refs.treegrid.collapseAll();
64  }
65
66  handleCustomRating() {
67    this.showCustomTypes = true;
68    this.columns = [...COLS];
69    this.buttonDisabled = true;
70  }
71}

On initialization, the custom data type column is empty because the showCustomTypes property is false. The constructor() method removes the customRating column from the columns object and the handleCustomRating() method sets all columns on the columns object.

Additionally, the handleExpandAll() method calls the expandAll() public method on the lightning-tree-grid component to expand all nested rows. And the handleCollapseAll() method calls the collapseAll() public method on the lightning-tree-grid component. In both cases, the lwc:ref directive locates the lightning-tree-grid component in the DOM using a unique ID you specify.

Release Preview

This release is in preview. Features described here don't become generally available until the latest general availability date that Salesforce announces for this release. Before then, and where features are noted as beta, pilot, or developer preview, we can't guarantee general availability within any particular time frame or at all. Make your purchase decisions only on the basis of generally available products and features.