If you are a Salesforce Lightning Developer there is a good possibility that you would have used Data Table component at-least once. It is one of the mostly used Aura Components which has its own advantages and pitfalls.
Let me throw some light on the advantages to start with, post which I will try to focus on pitfalls and finally we will look at how to implement a Custom Data Table in Lightning Experience.
Pros
- You get a visually appealing tabular structure just like
<apex:pageBlockTable>with all sort of options like inline-editing, static and dynamic menu items for each row, infinite loading and etc. - You can programatically select the rows of a Table.
- It's very easy to get the selected rows in client-side controller. There are predefined attributes and methods that we can make use of to access the selected rows.
- By adding a few configurations here and there we can make sure the Data Table has infinite loading feature.
- Customisable just by modifying the JSON configuration string in client-side controller and etc.
Cons
- It's not possible to have pick-list fields and lookup fields in Data Table.
- Row Highlighting is not possible.
- Anything which has got something to do with accessing the DOM is not possible.
The component that I created tries to act as a bridge between the Pros and Cons. I tried creating a Custom Data Table that has pick-list field as one of it column. If you understand the logic that's used in the component it becomes lot more easy to extend this to fields like lookup fields and other fields of that sort.
So lets get started ..
<aura:component implements="force:appHostable" controller="customDataTablesController">
<aura:handler name="init" value="{! this }" action="{! c.doInit }" />
<aura:attribute name="allAccounts" type="Account[]" />
<lightning:card title="Custom Data Table">
<table class="slds-table slds-table_cell-buffer slds-table_bordered">
<thead>
<tr class="slds-line-height_reset">
<th class="slds-text-title_caps" scope="col">
<div class="slds-truncate" title="Account Name">Account Name</div>
</th>
<th class="slds-text-title_caps" scope="col">
<div class="slds-truncate" title="Account Type">Account Type</div>
</th>
<th class="slds-text-title_caps" scope="col">
<div class="slds-truncate" title="Industry">Industry</div>
</th>
<th class="slds-text-title_caps" scope="col">
<div class="slds-truncate" title="Rating">Rating</div>
</th>
</tr>
</thead>
<tbody>
<!-- We are not trying to display the records related information
here, rather, we are trying to pass every snigle record to
another child component <c:customDataTableRow /> and this child component will take
care of getting the fields of a record displayed. -->
<aura:iteration items="{! v.allAccounts }" var="accountRec">
<c:customDataTableRow account="{!accountRec}"/>
</aura:iteration>
</tbody>
</table>
</lightning:card>
</aura:component>
({
doInit : function(component, event, helper) {
// Making a call to Apex method to fetch the account records.
var action = component.get("c.fetchAccounts");
//Defining the callback function
action.setCallback(this, function(response) {
var state = response.getState();
if(state === "SUCCESS"){
component.set( "v.allAccounts", response.getReturnValue());
}
else if(state === "INCOMPLETE"){
}
else if(state === "ERROR"){
var errors = response.getError();
if(errors){
if(errors[0] && errors[0].message){
console.log("Error message: " + errors[0].message);
}
}else{
console.log("Unknown error");
}
}
});
//Adding the request to queue
$A.enqueueAction(action);
}
})
<aura:component controller="customDataTablesController">
<aura:handler name="init" value="{! this }" action="{! c.doInit }" />
<aura:attribute name="options" type="List" access="PRIVATE"/>
<aura:attribute name="selectedValue" type="String" access="PRIVATE"/>
<aura:attribute name="account" type="Account" />
<tr class="slds-hint-parent">
<td data-label="{!v.account.Name}" scope="row">
<div class="slds-truncate" title="{!v.account.Name}">{!v.account.Name}</div>
</td>
<td data-label="{!v.account.Type}">
<div class="slds-truncate" title="{!v.account.Type}">{!v.account.Type}</div>
</td>
<td data-label="{!v.account.Industry}">
<div class="slds-truncate" title="{!v.account.Industry}">{!v.account.Industry}</div>
</td>
<td data-label="{!v.account.Rating}">
<div class="slds-truncate" title="{!v.account.Rating}">
<!-- This is where we get the dyanamic values from the client-side
controller and display it here -->
<lightning:select name="colorId" aura:id="colorId" value="{!v.selectedValue}">
<aura:iteration items="{!v.options}" var="option">
<option text="{!option.label}" value="{!option.id}" selected="{!option.selected}"/>
</aura:iteration>
</lightning:select>
</div>
</td>
</tr>
</aura:component>
({
doInit : function(component, event, helper) {
//Making a call to the Apex method to fetch the values of Rating field
var action = component.get("c.fetchRatingValues");
//Getting the value of Rating field for a particular record from Component
var rating = component.get(' v.account.Rating');
action.setCallback(this, function(response) {
var state = response.getState();
if (state === "SUCCESS") {
console.log(' 💥 ' + response.getReturnValue());
var allRatingValues = response.getReturnValue();
/** We need to form a JSON object like this to pass it to
<lightning:select /> tag */
/** var serverResponse = {
selectedColorId: 2,
colors: [
{ id: 1, label: 'Red' },
{ id: 2, label: 'Green', selected: true },
{ id: 3, label: 'Blue' }
]
};
*/
var ratingId;
var ratingOptions = [];
for(var i=0; i< allRatingValues.length; i++){
if(i == 0){
/** We need to make sure the first value of the Picklist
is always None */
if(rating != undefined){
ratingOptions.push({
id: 0,
label: '--None--'
});
}else{
ratingOptions.push({
id: 0,
label: '--None--',
selected: true
});
ratingId = 0;
}
}
/** Am trying to compare the Rating field value with the list
that we get back from the Apex method and when there is a match
am adding selected: true to the JSON Object. */
if(allRatingValues[i] == rating){
ratingOptions.push({
id: i+1,
label: allRatingValues[i],
selected: true
});
ratingId = i+1;
}else{
ratingOptions.push({
id: i+1,
label: allRatingValues[i]
});
}
}
console.log(' 💥 ' + JSON.stringify(ratingOptions));
var serverResponse = {
selectedRatingId: ratingId,
rating: ratingOptions
};
component.set( "v.options", serverResponse.rating);
component.set( "v.selectedValue", serverResponse.selectedRatingId);
}
else if(state === "INCOMPLETE"){
}
else if(state === "ERROR"){
var errors = response.getError();
if(errors){
if(errors[0] && errors[0].message) {
console.log("Error message: " + errors[0].message);
}
}else{
console.log("Unknown error");
}
}
});
$A.enqueueAction(action);
}
})
public class customDataTablesController {
@AuraEnabled
public static List<Account> fetchAccounts(){
return [SELECT Id, Name, Industry, Type, Rating FROM ACCOUNT LIMIT 10];
}
/**
This method will fetch us all the values of Rating Picklist in Account
SObject dynamically. We are trying to form a List of values related to
Rating Picklist. We cannot use `SelectOption` class here as it's not a
aura enabled class.
*/
@AuraEnabled
public static List<String> fetchRatingValues(){
//List<SelectOption> options = new List<SelectOption>();
List<String> ratingOptions = new List<String>();
Schema.DescribeFieldResult fieldResult = Account.Rating.getDescribe();
List<Schema.PicklistEntry> ple = fieldResult.getPicklistValues();
/**for(Schema.PicklistEntry f : ple){
options.add(new SelectOption(f.getLabel(),f.getValue()));
}*/
for(Schema.PicklistEntry a: ple){
ratingOptions.add(a.getValue());
}
return ratingOptions;
}
}
In case you have any questions, let me know in the comments below.
Related Posts
Learn it by building it
Every course, every code repository, the discussion group and certificates of completion — one subscription.