A lightning:datatable component displays tabular data where each column can be displayed based on the data type. For example, an email address is displayed as a hyperlink with the mailto: URL scheme by specifying the email type. The default type is text.
This component inherits styling from data tables in the Lightning Design System.
Tables can be populated during initialization using the data, columns, and keyField attributes. This example creates a table with 6 columns, where the first column displays a checkbox for row selection. The table data is loaded using the init handler. Selecting the checkbox enables you to select the entire row of data and triggers the onrowselection event handler.
<aura:component> <aura:attribute name="mydata" type="Object"/> <aura:attribute name="mycolumns" type="List"/> <aura:handler name="init" value="{! this }" action="{! c.init }"/> <lightning:datatable data="{! v.mydata }" columns="{! v.mycolumns }" keyField="id" onrowselection="{! c.getSelectedName }"/> </aura:component>
Here's the client-side controller that creates selectable rows and the columns object to their corresponding column data. The Confidence column displays percentages with an icon that denotes the increasing or decreasing confidence trend.
({ init: function (cmp, event, helper) { cmp.set('v.mycolumns', [ {label: 'Opportunity name', fieldName: 'opportunityName', type: 'text'}, {label: 'Confidence', fieldName: 'confidence', type: 'percent', cellAttributes: { iconName: { fieldName: 'trendIcon' }, iconPosition: 'right' }}, {label: 'Amount', fieldName: 'amount', type: 'currency', typeAttributes: { currencyCode: 'EUR'}}, {label: 'Contact Email', fieldName: 'contact', type: 'email'}, {label: 'Contact Phone', fieldName: 'phone', type: 'phone'} ]); cmp.set('v.mydata', [{ id: 'a', opportunityName: 'Cloudhub', confidence: 0.2, amount: 25000, contact: 'jrogers@cloudhub.com', phone: '2352235235', trendIcon: 'utility:down' }, { id: 'b', opportunityName: 'Quip', confidence: 0.78, amount: 740000, contact: 'quipy@quip.com', phone: '2352235235', trendIcon: 'utility:up' }]); }, getSelectedName: function (cmp, event) { var selectedRows = event.getParam('selectedRows'); // Display that fieldName of the selected rows for (var i = 0; i < selectedRows.length; i++){ alert("You selected: " + selectedRows[i].opportunityName); } } })
In the previous example, the first row of data displays a checkbox in the first column, and columns with the following data: Cloudhub, 20%, $25,000.00, jrogers@cloudhub.com, and (235) 223-5235. The last two columns are displayed as hyperlinks to represent an email address and telephone number.
Retrieving Data Using an Apex Controller
Let's say you want to display data on the Contact object. Create an Apex controller that queries the fields you want to display.
public with sharing class ContactController { @AuraEnabled public static List<Contact> getContacts() { List<Contact> contacts = [SELECT Id, Name, Phone, Email FROM Contact]; //Add isAccessible() check return contacts; } }
Wire this up to your component via the controller attribute. The markup looks similar to the previous example.
<aura:component controller="ContactController"> <aura:attribute name="mydata" type="Object"/> <aura:attribute name="mycolumns" type="List"/> <aura:handler name="init" value="{! this }" action="{! c.init }"/> <lightning:datatable data="{! v.mydata }" columns="{! v.mycolumns }" keyField="Id" hideCheckboxColumn="true"/> </aura:component>
Initialize the column data by mapping the fieldName property to the API name of the field.
({ init: function (cmp, event, helper) { cmp.set('v.mycolumns', [ {label: 'Contact Name', fieldName: 'Name', type: 'text'}, {label: 'Phone', fieldName: 'Phone', type: 'phone'}, {label: 'Email', fieldName: 'Email', type: 'email'} ]); helper.getData(cmp); } })
Finally, retrieve the contacts in your helper.
({ getData : function(cmp) { var action = cmp.get('c.getContacts'); action.setCallback(this, $A.getCallback(function (response) { var state = response.getState(); if (state === "SUCCESS") { cmp.set('v.mydata', response.getReturnValue()); } else if (state === "ERROR") { var errors = response.getError(); console.error(errors); } })); $A.enqueueAction(action); } })
Working with Column Data
Besides providing the column data, you must define the following column properties.
Property | Type | Description |
---|---|---|
label | string | Required. The text label displayed in the column header. |
fieldName | string | Required. The name that binds the columns properties to the associated data. Each columns property must correspond to an item in the data array. |
type | string | Required. The data type to be used for data formatting. For more information, see Formatting with Data Types. |
initialWidth | integer | The width of the column when it's initialized, which must be within the minColumnWidth and maxColumnWidth values, or within 50px and 1000px if they are not provided. |
typeAttributes | object | Provides custom formatting with component attributes for the data type. For example, currencyCode for the currency type. For more information, see Formatting with Data Types. |
cellAttributes | object | Provides additional customization, such as appending an icon to the output. For more information, see Appending an Icon to Column Data |
sortable | boolean | Specifies whether sorting by columns is enabled. The default is false. |
actions | object | Appends a dropdown menu of actions to a column. You must pass in a list of label-name pairs. |
iconName | string | The Lightning Design System name of the icon. Names are written in the format standard:opportunity. The icon is appended to the left of the header label. |
Formatting with Data Types
The data table determines the format based on the type you specify. Each data type is associated to a base Lightning component. For example, specifying the text type renders the associated data using a lightning:formattedText component. Some of these types allow you to pass in the attributes via the typeAttributes property to customize your output. For supported attribute values, refer to the component’s reference documentation. Valid data types and their supported attributes include:
Type | Description | Supported Type Attributes |
---|---|---|
action | Displays a dropdown menu using lightning:buttonMenu with actions as menu items | rowActions (required), menuAlignment (defaults to right) |
button | Displays a button using lightning:button | disabled, iconName, iconPosition, label, name, title, variant |
currency | Displays a currency using lightning:formattedNumber | currencyCode, currencyDisplayAs |
date | Displays a date and time based on the locale using lightning:formattedDateTime | day, era, hour, hour12, minute, month, second, timeZone, timeZoneName, weekday, year |
Displays an email address using lightning:formattedEmail | N/A | |
location | Displays a latitude and longitude of a location using lightning:formattedLocation | latitude, longitude |
number | Displays a number using lightning:formattedNumber | minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, minimumSignificantDigits, maximumSignificantDigits |
percent | Displays a percentage using lightning:formattedNumber | Same as number type |
phone | Displays a phone number using lightning:formattedPhone | N/A |
text | Displays text using lightning:formattedText | N/A |
url | Displays a URL using lightning:formattedUrl | label, target |
To customize the formatting based on the data type, pass in the attributes for the corresponding base Lightning component. For example, pass in a custom currencyCode value to override the default currency code.
columns: [ {label: 'Amount', fieldName: 'amount', type: 'currency', typeAttributes: { currencyCode: 'EUR' }} // other column data ]
When using currency or date and time types, the default user locale is used when no locale formatting is provided.
For more information on attributes, see the corresponding component documentation.
Appending an Icon to Column DataTo append an icon to your data output, use cellAttributes and pass in these attributes.
Attribute | Description |
---|---|
iconName | Required. The Lightning Design System name of the icon, for example, utility:down. |
iconLabel | The label for the icon to be displayed on the right of the icon. |
iconPosition | The position of the icon relative to the data. Valid options include left and right. This value defaults to left. |
You can add an icon in several ways.
var columns = [ // simple icon {label: 'Close date', fieldName: 'closeDate', type: 'date', sortable: true, cellAttributes: { iconName: 'utility:event' }}, // icon appended with a label {label: 'Confidence', fieldName: 'confidence', type: 'percent', cellAttributes: { iconName: { fieldName: 'confidenceDeltaIcon' }, iconLabel: { fieldName: 'confidenceDelta' }, iconPosition: 'right' }} // other column data ]
Infinite scrolling enables you to load a subset of data and then display more when users scroll to the end of the table. To enable infinite scrolling, set enableInfiniteLoading to true and provide an event handler using onloadmore. By default, data loading is triggered when you scroll down to 20px from the bottom of the table, but the offset can be changed using the loadMoreOffset attribute.
This example loads 50 more rows from the database when you reach the end of the table until there are no more data to load. It assumes that you're retrieving data in an init handler similar to the one described in "Retrieving Data Using an Apex Controller".
<aura:component controller="ContactController"> <aura:attribute name="columns" type="List" default="[]"/> <aura:attribute name="data" type="List" default="[]"/> <aura:attribute name="enableInfiniteLoading" type="Boolean" default="true"/> <aura:attribute name="initialRows" type="Integer" default="30"/> <aura:attribute name="rowsToLoad" type="Integer" default="50"/> <aura:attribute name="totalNumberOfRows" type="Integer" default="300"/> <aura:attribute name="loadMoreStatus" type="String" default=""/> <!-- Retrieve your data using an Apex Controller --> <aura:handler name="init" value="{! this }" action="{! c.init }"/> <div style="height: 500px"> <lightning:datatable columns="{! v.columns }" data="{! v.data }" keyField="id" enableInfiniteLoading="true" onloadmore="{! c.loadMoreData }"/> </div> {! v.loadMoreStatus } </aura:component>
The onloadmore event handler retrieves more data when you scroll to the bottom of the table until there are no more data to load. To display a spinner while data is being loaded, set the isLoading attribute to true.
({ loadMoreData: function (cmp, event, helper) { //Display a spinner to signal that data is being loaded event.getSource().set("v.isLoading", true); //Display "Loading" when more data is being loaded cmp.set('v.loadMoreStatus', 'Loading'); helper.fetchData(cmp, cmp.get('v.rowsToLoad')) .then($A.getCallback(function (data) { if (cmp.get('v.data').length >= cmp.get('v.totalNumberOfRows')) { cmp.set('v.enableInfiniteLoading', false); cmp.set('v.loadMoreStatus', 'No more data to load'); } else { var currentData = cmp.get('v.data'); //Appends new data to the end of the table var newData = currentData.concat(data); cmp.set('v.data', newData); cmp.set('v.loadMoreStatus', ''); } event.getSource().set("v.isLoading", false); })); } })
While this example uses a fixed number to denote the total number of rows, you can also use the SOQL SELECT syntax with the COUNT() function to return the number of rows in the object in your Apex controller. Then, set the result on the totalNumberOfRows attribute during initialization.
SELECT COUNT(Id) FROM Contact
Header-level actions refer to tasks you can perform on a column of data, such as displaying only rows that meet a criteria provided by the column. You can perform actions on a column and handle them using the onheaderaction event handler.
Supported attributes for the header actions are as follows.
Attribute | Description |
---|---|
label | Required. The label that's displayed for the action. |
name | Required. The name of the action, which identifies the selected action. |
checked | Specifies whether a check mark is shown to the left of the action label. If true, a check mark is shown to the left of the menu item. If false, a check mark is not shown but there is space to accommodate one. |
disabled | Specifies whether the action can be selected. If true, the action item is shown as disabled. This value defaults to false. |
iconName | The name of the icon to be displayed to the right of the action item. |
For example, you want to create a filter that displays only rows where the Publishing State column matches either the Published or Unpublished state.
<aura:attribute name="rawData" type="Object" description="Stores the currently displayed rows of data"/> <aura:attribute name="activeFilter" type="string" default="all" description="The currently selected actions filter"/> <lightning:datatable columns="{! v.mycolumns }" data="{! v.mydata }" keyField="id" onheaderaction="{! c.handleHeaderAction }"/>
Bind the header actions to the actions column property, which can be done during initialization.
({ init: function (cmp, event, helper) { var headerActions = [ { label: 'All', checked: true, name:'all' }, { label: 'Published', checked: false, name:'show_published' }, { label: 'Unpublished', checked: false, name:'show_unpublished' }, ]; cmp.set('v.mycolumns', [ // other column data { label: 'Publishing State', fieldName: 'published', type: 'text', actions: headerActions } ]); }, handleHeaderAction: function (cmp, event, helper) { // Retrieves the name of the selected filter var actionName = event.getParam('action').name; // Retrieves the current column definition // based on the selected filter var colDef = event.getParam('columnDefinition'); var columns = cmp.get('v.mycolumns'); var activeFilter = cmp.get('v.activeFilter'); if (actionName !== activeFilter) { var idx = columns.indexOf(colDef); // Update the column definition with the updated actions data var actions = columns[idx].actions; actions.forEach(function (action) { action.checked = action.name === actionName; }); cmp.set('v.activeFilter', actionName); helper.updateBooks(cmp); cmp.set('v.mycolumns', columns); } } })
Finally, display the rows to match the selected filter, which is performed by helper.updateBooks(cmp).
({ updateBooks: function (cmp) { var rows = cmp.get('v.rawData'); var activeFilter = cmp.get('v.activeFilter'); var filteredRows = rows; if (activeFilter !== 'all') { filteredRows = rows.filter(function (row) { return (activeFilter === 'show_published' || (activeFilter === 'show_unpublished'); }); } cmp.set('v.mydata', filteredRows); } })
Row-level actions refer to tasks you can perform on a row of data, such as updating or deleting the row. Static actions apply to all rows on the table. You can perform actions on each row and handle them using the onrowaction event handler.
Supported attributes for the header actions are as follows.
Attribute | Description |
---|---|
label | Required. The label that's displayed for the action. |
name | Required. The name of the action, which identifies the selected action. |
disabled | Specifies whether the action can be selected. If true, the action item is shown as disabled. This value defaults to false. |
iconName | The name of the icon to be displayed to the right of the action item. |
<lightning:datatable columns="{! v.mycolumns }" data="{! v.mydata }" keyField="id" onrowaction="{! c.handleRowAction }"/>
You must provide a list of actions to the columns data, which can be done during initialization. This client-side controller initializes the actions column and handles the actions on each row, displaying the row details and deleting the row when the action is clicked.
({ init: function (cmp, event, helper) { var actions = [ { label: 'Show details', name: 'show_details' }, { label: 'Delete', name: 'delete' } ]; cmp.set('v.mycolumns', [ // Other column data here { type: 'action', typeAttributes: { rowActions: actions } } ]); }, handleRowAction: function (cmp, event, helper) { var action = event.getParam('action'); var row = event.getParam('row'); switch (action.name) { case 'show_details': alert('Showing Details: ' + JSON.stringify(row)); break; case 'delete': var rows = cmp.get('v.mydata'); var rowIndex = rows.indexOf(row); rows.splice(rowIndex, 1); cmp.set('v.mydata', rows); break; } })
To delete the record in the database, create an Apex controller with the delete operation.
@AuraEnabled public static Contact deleteContact(Contact contact) { delete contact; return contact; }
The code in the case 'delete' block can be moved to a helper function and replaced with:
helper.deleteRow(cmp, row);
Your helper looks like this. It calls the deleteContact Apex controller and passes in the row of data to be deleted. If the delete is successful, the row of data in the component is removed from view.
({ deleteRow: function(cmp, row) { var action = cmp.get("c.deleteContact"); action.setParams({ "contact":row }); action.setCallback(this, function(response) { var state = response.getState(); if (state === "SUCCESS") { var rows = cmp.get('v.mydata'); var rowIndex = rows.indexOf(row); rows.splice(rowIndex, 1); cmp.set('v.mydata', rows); } else if (state === "ERROR") { // handle error } }); $A.enqueueAction(action); } })
You can also update a row of data by using the Apex controller with the upsert operation. Alternatively, you can display the record edit page by firing the force:editRecord event. To associate the record edit page to the contacts, pass in "recordId": row.Id.
Creating Dynamic Row-Level ActionsDynamic actions are created based on the content of each row. When you click the dropdown menu, an asynchronous call is made to determine which actions to display for the particular row. The logic that determines which action to display can be created on initialization. In this example, the action and its label is evaluated when the dropdown menu is activated. Assume that we have an active column that displays the status of a contact (Active or Inactive), which determines which action to display (Deactivate or Activate).
({ init: function (cmp, event, helper) { var actions = helper.getRowActions.bind(this, cmp); cmp.set('v.mycolumns', [ // Other column data here { label: 'State', fieldName: 'active', type: 'text' }, { type: 'action', typeAttributes: { rowActions: actions } } ]); // Fetch or set your data }, handleRowAction: function (cmp, event, helper) { var action = event.getParam('action'); var row = event.getParam('row'); switch (action.name) { case 'activate': helper.activateContact(cmp, row) break; case 'deactivate': helper.deactivateContact(cmp, row) break; } } })
The helper evaluates which label (Activate or Deactivate) to display and updates the active column with the right value.
({ getRowActions: function (cmp, row, doneCallback) { var actions = []; if (row['isActive']) { actions.push({ 'label': 'Deactivate', 'iconName': 'utility:block_visitor', 'name': 'deactivate' }); } else { actions.push({ 'label': 'Activate', 'iconName': 'utility:adduser', 'name': 'activate' }); } // simulate a trip to the server setTimeout($A.getCallback(function () { doneCallback(actions); }), 200); }, activateContact: function (cmp, row) { var rows = cmp.get('v.data'); var rowIndex = rows.indexOf(row); rows[rowIndex]['isActive'] = true; rows[rowIndex]['active'] = 'Active'; cmp.set('v.data', rows); }, deactivateContact: function (cmp, row) { var rows = cmp.get('v.data'); var rowIndex = rows.indexOf(row); rows[rowIndex]['isActive'] = false; rows[rowIndex]['active'] = 'Inactive'; cmp.set('v.data', rows); } })
The previous example illustrates how to create and handle dynamic actions on the client-side only. You can make server calls and persist your record data changes via an Apex controller.
Resizing Tables and Columns
The width and height of the datatable is determined by the container element. A scroller is appended to the table body if there are more rows to display. For example, you can restrict the height to 300px by applying CSS styling to the container element.
<div style="height: 300px;"> <!-- lightning:datatable goes here --> </div>
By default, columns are resizable. Users can click and drag the width to a minimum of 50px and a maximum of 1000px, unless the default values are changed. Columns can be resized by default. You can disable column resizing by setting resizeColumnDisabled to true. To change the minimum and maximum width column, use the minColumnWidth and maxColumnWidth attributes.
Selecting Rows ProgrammaticallyThe selectedRows attribute enables programmatic selection of rows, which is useful when you want to preselect rows.
<aura:component> <!-- Attributes and init handler here --> <lightning:datatable columns="{! v.columns }" data="{! v.data }" keyField="id" selectedRows="{! v.selectedRows }" /> <lightning:button label="Select" onclick="{!c.handleSelect}"/> </aura:component>
To select a row programmatically, pass in the row keyField value.
({ // Load data via init handler first // then handle programmatic selection handleSelect: function (cmp, event, helper) { var rows = ['a']; cmp.set('v.selectedRows', rows); } })
If maxRowSelection is set to a value less than the number of selected rows, only the specified number of rows will be selected. For example, if you set maxRowSelection to 2 and pass in ['a', 'b', 'c'] to selectedRows, only rows a and b will be selected.
Sorting Data By Column
To enable sorting of row data by a column label, set sortable to true for the column on which you want to enable sorting. Set sortedBy to match the fieldName property on the column. Clicking a column header sorts rows by ascending order unless the defaultSortDirection is changed, and clicking it subsequently reverses the order. Handle the onsort event handler to update the table with the new column index and sort direction.
Here's an example of the client-side controller that's called by the onsort event handler.
({ // Client-side controller called by the onsort event handler updateColumnSorting: function (cmp, event, helper) { var fieldName = event.getParam('fieldName'); var sortDirection = event.getParam('sortDirection'); // assign the latest attribute with the sorted column fieldName and sorted direction cmp.set("v.sortedBy", fieldName); cmp.set("v.sortedDirection", sortDirection); helper.sortData(cmp, fieldName, sortDirection); } })
The helper function is as follows.
({ sortData: function (cmp, fieldName, sortDirection) { var data = cmp.get("v.data"); var reverse = sortDirection !== 'asc'; //sorts the rows based on the column header that's clicked data.sort(this.sortBy(fieldName, reverse)) cmp.set("v.data", data); }, sortBy: function (field, reverse, primer) { var key = primer ? function(x) {return primer(x[field])} : function(x) {return x[field]}; //checks if the two rows should switch places reverse = !reverse ? 1 : -1; return function (a, b) { return a = key(a), b = key(b), reverse * ((a > b) - (b > a)); } } })
You can wrap or clip text within columns, which either expands the rows to reveal more content or truncate the content to a single line within the column.
To toggle between the two views, select Wrap text or Clip text from the dropdown menu on the column header.
If the number of characters is more than what the column width can hold, content is clipped by default. Text wrapping is supported only for the following data types.
Accessibility
You can use data tables in navigation mode and action mode using the keyboard. To enter navigation mode, tab into the data table, which triggers focus on the first data cell in the table body. Use the arrow keys to move around the table.
To enter action mode, press the Enter key or Space Bar. Columns can be resized in action mode. To resize a column, navigate to the header by pressing the Up Arrow key. Then, press the Tab key to activate the column divider, and resize the column using the Left Arrow and Right Arrow key. To finish resizing the column and return to navigation mode, press the Tab key.
When focus is on a cell that contains a link, pressing enter to navigate to the link is currently not supported. This limitation applies to cells that contain data of type url, phone, and email.
Methods
This component supports the following method.
getSelectedRows(): Returns an array of data in each selected row.
Attribute Name | Attribute Type | Description | Required? |
---|---|---|---|
body | Component[] | The body of the component. In markup, this is everything in the body of the tag. | |
class | String | A CSS class for the outer element, in addition to the component's base classes. | |
columns | List | Array of the columns object that's used to define the data types. Required properties include 'label', 'dataKey', and 'type'. The default type is 'text'. | |
data | Object | The array of data to be displayed. | |
defaultSortDirection | String | Specifies the default sorting direction on an unsorted column. Valid options include 'asc' and 'desc'. The default is 'asc' for sorting in ascending order. | |
enableInfiniteLoading | Boolean | Enables or disables infinite loading. The default is false. | |
hideCheckboxColumn | Boolean | Hides or displays the checkbox column for row selection. To hide the checkbox column, set hideCheckboxColumn to true. The default is false. | |
isLoading | Boolean | Specifies whether more data is being loaded and displays a spinner if so. The default is false. | |
keyField | String | Required for better performance. Associates each row with a unique ID. | Yes |
loadMoreOffset | Integer | Determines when to trigger infinite loading based on how many pixels the table's scroll position is from the bottom of the table. The default is 20. | |
maxColumnWidth | Integer | The maximum width for all columns. The default is 1000px. | |
maxRowSelection | Integer | The maximum number of rows that can be selected. Checkboxes are used for selection by default, and radio buttons are used when maxRowSelection is 1. | |
minColumnWidth | Integer | The minimum width for all columns. The default is 50px. | |
onheaderaction | Action | The action triggered when a header action is clicked. By default, it also closes the header actions menu. | |
onloadmore | Action | The action triggered when infinite loading loads more data. | |
onresize | Action | The action triggered when the table renders columns the first time and every time its resized an specific column. | |
onrowaction | Action | The action triggered when a row action is clicked. By default, it also closes the row actions menu. | |
onrowselection | Action | The action triggered when a row is selected. | |
onsort | Action | The action triggered when a column is sorted. | |
resizeColumnDisabled | Boolean | Specifies whether column resizing is disabled. The default is false. | |
resizeStep | Integer | The width to resize the column when user press left or right arrow. The default is 10px. | |
rowNumberOffset | Integer | Determines where to start counting the row number. The default is 0. | |
selectedRows | List | Enables programmatic row selection with a list of keyField values. | |
showRowNumberColumn | Boolean | Shows or hides the row number column. Set to true to show the row number column. The default is false. | |
sortedBy | String | The column fieldName that controls the sorting order. Sort the data using the onsort event handler. | |
sortedDirection | String | Specifies the sorting direction. Sort the data using the onsort event handler. Valid options include 'asc' and 'desc'. | |
title | String | Displays tooltip text when the mouse moves over the element. |