Nowadays, there are relatively many Lightning Web Components available that already cover a considerable amount of use cases.
Nevertheless, developers still need to extend the functionality in some spots.
For instance, consider adding custom field types to the data table or dynamically updating its elements' styles.
Unfortunately, we cannot manipulate the DOM of the standard LWC outside of it.
But we can create a new LWC and extend the standard class.
In this post, I will explain how to do just that.
Extend Properties
Consider the following case.
I want to add a new custom type to the data table to render a button with the dynamic label. Pressing that button should emit a custom data table event.
First of all, we need to create an LWC for the button. It accepts the data from the table via the attributes and emits a custom event on click to expose it to the parent components.
// inlineButton.js
import { LightningElement, api } from "lwc";
export default class DatatableInlineButton extends LightningElement {
@api label;
@api value;
@api field;
@api context;
handleClick(event) {
// fire event to send context and selected value to the data table
this.dispatchEvent(
new CustomEvent("inlineclick", {
composed: true,
bubbles: true,
cancelable: true,
detail: {
data: { context: this.context, value: this.value, field: this.field },
},
}),
);
}
}
// inlineButton.html
<template>
<lightning-button
variant="base"
label="{value}"
title="{value}"
onclick="{handleClick}"
></lightning-button>
</template>
Next, we need to create an LWC to extend the standard lightning-datatable component.
Note: Remove the HTML file as you don't need it.
// customDataTable.js
import LightningDatatable from "lightning/datatable";
export default class CustomDataTable extends LightningDatatable {
constructor() {
super();
}
}
We can import standard components from the lightning module and extend its class.
Next, we need to define the custom column type and specify its cell template.
Create the following template within the LWC directory.
// inline-button-template.html
<template>
<c-inline-button
label="{typeAttributes.label}"
value="{typeAttributes.value}"
field="{typeAttributes.field}"
context="{typeAttributes.context}"
></c-inline-button>
</template>
Once we have the template, we can reference it in the custom types configuration. Here is how the custom table should look now.
// customDataTable.js
import LightningDatatable from "lightning/datatable";
import DatatableInlineButtonTemplate from "./inline-button-template.html";
export default class CustomDataTable extends LightningDatatable {
static customTypes = {
inlinebutton: {
template: DatatableInlineButtonTemplate,
typeAttributes: ["label", "value", "context", "field"],
},
};
constructor() {
super();
}
}
Great, now we can start using a new custom data type.
Given the list of tasks to render in the data table
data = [
{
Id: "1",
OwnerName: "John Appleseed",
},
];
We can specify the inline button custom type to render it inside the table cell.
columns = [{
{
label: 'Assigned to',
fieldName: 'OwnerName',
type: 'inlinebutton', // custom type key
typeAttributes: {
field: 'OwnerName' ,
value: { fieldName: 'OwnerName' },
context: { fieldName: 'Id' }, // bind data key to retrieve the value
},
},
}];
And you can add a handler to a new custom event that we created earlier.
<c-custom-data-table
data="{data}"
columns="{columns}"
oninlineclick="{handleInlineClick}"
></c-custom-data-table>
Extend Methods
Likewise the properties, you can also extend the methods of the parent class.
For instance, I want to add a title attribute to each row in the table to display the assignee name on the mouse over.
We can manipulate the DOM once it is available after the render.
By default, lightning web components have the "renderedCallback" method for that purpose.
The "lightning-datatable" component already uses that callback to render the template correctly, so we need to extend its functionality to add our logic there.
renderedCallback() {
super.renderedCallback(); // call parent callback
const rows = Array.from(this.template.querySelectorAll('tr[data-row-key-value]'));
rows.map(row => {
const id = row.dataset.rowKeyValue;
const dataRow = this.data.find(item => item.Id === id);
row.setAttribute('title', dataRow.OwnerName) // set asignee name as the row title
})
}
Conclusion
Like mentioned at the beginning, Salesforce already provides many components out of the box. But occasionally, there is a need to change something or enhance the standard components. That is where extending a component functionality becomes handy.
I hope that you find this post helpful. Happy coding!
Nikita Verkhoshintcev
Salesforce Freelance Developer / Solution Architect
I'm a senior freelance Salesforce and full-stack web developer based in Helsinki, Finland. I help companies, consulting agencies, and ISV partners build custom Salesforce applications.