function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Holly Havelka 10Holly Havelka 10 

Help With Basic Lightning Component

Hi all,

I have the following custom lightning component, but it is coming up with the following error: Unknown controller action 'getOpps'.

component:
<aura:component controller="AcctOppsController" implements="flexipage:availableForRecordHome,force:hasRecordId">
    <aura:attribute name="mydata" type="OpportunityContactRole[]"/>
    <aura:attribute name="mycolumns" type="List"/>
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="currentRecordId" type="Id" />
    <aura:handler name="init" value="{!this }" action="{! c.doInit }"/>
    <div style="height: 300px">
        <lightning:datatable
                keyField="id"
                data="{! v.mydata }"
                columns="{! v.mycolumns }"
                hideCheckboxColumn="true"/>
    </div>
</aura:component>
controller:
({
    doInit: function (cmp, event, helper) {
        cmp.set('v.mycolumns', [
            {label: 'Opportunity Name', fieldName: 'opportunityId', type: 'text'},
            {label: 'Contact Name', fieldName: 'contact', type: 'text'},
            {label: 'Role', fieldName: 'role', type: 'text'},
            {label: 'Amount', fieldName: 'amount', type: 'currency'}
        ]);
        
        var fetchData = {
            opportunityId: "opportunityId",
            contact : "Contact.Name",
            role : "Role",
            amount : "Opportunity.Amount"
        };

        helper.fetchData(cmp,fetchData);
    }
})
helper:
({
    fetchData : function(cmp) {
        var recordId =  cmp.get("v.recordId");
        var action = cmp.get("c.getOpps");
        action.setParams({ "currentRecordId" : recordId });
        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);
    }
})
apex controller:
public with sharing class AcctOppsController{
@AuraEnabled
public String currentRecordId {get;set;}

    public AcctOppsController(ApexPages.StandardController controller) {
        currentRecordId  = ApexPages.CurrentPage().getparameters().get('id');
    }
    
    @AuraEnabled    
    public List<OpportunityContactRole> getOpps() {
        List<OpportunityContactRole> oppresults = [SELECT Contact.name, Role, OpportunityId, Opportunity.Amount, Opportunity.StageName, Opportunity.Type FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId];
        return oppresults;
        }
}

Any thoughts on what I am missing?


 

Best Answer chosen by Holly Havelka 10
Alain CabonAlain Cabon
Hi,

With a new object :  let ob = {};   it is simpler.
 
({
    readData : function(cmp) {
        var recordId =  cmp.get("v.recordId");
        var action = cmp.get("c.myOpps");
        action.setParams({ "currentRecordId" : recordId });
        action.setCallback(this, $A.getCallback(function (response) {
            var state = response.getState();
            var res = [];
            if (state ==="SUCCESS") {
                var resp = response.getReturnValue();
                console.log(resp);
                resp.forEach(function(item, index, array) {                
                    let ob = {};
                    ob.opportunityName = item.Opportunity.Name;
                    ob.contact = item.Contact.Name;
                    ob.role = item.Role;
                    ob.amount = item.Opportunity.Amount;
                    res.push(ob);
                });
                cmp.set('v.mydata',res);
                
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
            }
        }
                                               ));
        $A.enqueueAction(action);
    },
})

({
    doInit: function (cmp, event, helper) {
        cmp.set('v.mycolumns', [
            {label: 'Opportunity Name', fieldName: 'opportunityName', type: 'text'},
            {label: 'Contact Name', fieldName: 'contact', type: 'text'},
            {label: 'Role', fieldName: 'role', type: 'text'},
            {label: 'Amount', fieldName: 'amount', type: 'currency'}
        ]);
         
        helper.readData(cmp);
    }
})

public with sharing class AcctOppsController{
    
    @AuraEnabled    
    public static List<Object> myOpps(String currentRecordId) {
        List<OpportunityContactRole> oppresults = [SELECT Contact.name, Role, OpportunityId, Opportunity.Amount,Opportunity.Name, Opportunity.StageName, Opportunity.Type FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId]; 
        return oppresults;
    }
}


<aura:attribute name="mydata" type="Object[]"/> : generic array of objects
 
<aura:component controller="AcctOppsController" implements="flexipage:availableForRecordHome,force:hasRecordId">
    <aura:attribute name="mydata" type="Object[]"/>
    <aura:attribute name="mycolumns" type="List"/>
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="currentRecordId" type="Id" />
    <aura:handler name="init" value="{!this }" action="{! c.doInit }"/>
    <div style="height: 300px">
        <lightning:datatable
                keyField="id"
                data="{! v.mydata }"
                columns="{! v.mycolumns }"
                hideCheckboxColumn="true"/>
    </div>
</aura:component>

 

All Answers

Raj VakatiRaj Vakati
try this code
 
@AuraEnabled
public String currentRecordId {get;set;}

    public AcctOppsController(ApexPages.StandardController controller) {
        currentRecordId  = ApexPages.CurrentPage().getparameters().get('id');
    }
	public AcctOppsController(){
	}
    
    @AuraEnabled    
    public List<OpportunityContactRole> getOpps() {
        List<OpportunityContactRole> oppresults = [SELECT Contact.name, Role, OpportunityId, Opportunity.Amount, Opportunity.StageName, Opportunity.Type FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId];
        return oppresults;
        }
}

 
Raj VakatiRaj Vakati
Its constructor issue 
Holly Havelka 10Holly Havelka 10
Raj V, I tried your code, but I am still getting the same error.
Raj VakatiRaj Vakati
method should be static .all AuraEnabled  method should be static 
 
@AuraEnabled    
    public static List<OpportunityContactRole> getOpps() {
        List<OpportunityContactRole> oppresults = [SELECT Contact.name, Role, OpportunityId, Opportunity.Amount, Opportunity.StageName, Opportunity.Type FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId];
        return oppresults;
        }

 
Holly Havelka 10Holly Havelka 10
Here is my updated controller: 
public with sharing class AcctOppsController{
@AuraEnabled
public String currentRecordId {get;set;}

    public AcctOppsController(ApexPages.StandardController controller) {
        currentRecordId  = ApexPages.CurrentPage().getparameters().get('id');
    }
    public AcctOppsController(){
    }
    
    @AuraEnabled    
    public static List<OpportunityContactRole> getOpps(Id currentRecordId) {
        
        List<OpportunityContactRole> oppresults = [SELECT Contact.name, Role, OpportunityId, Opportunity.Amount, Opportunity.StageName, Opportunity.Type FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId];
        return oppresults;
        }
}
Nothing is loading though:
Component
Jim JamJim Jam
Try renaming the getOpps method parameter (Id currentRecordId), so it doesn't clash with the public property you have which has the same name (line 3). Obviously, make sure the parameter name you pass in from the lightning component matches the paramater name.
Holly Havelka 10Holly Havelka 10
Still not working with your suggestion.
Raj VakatiRaj Vakati
What value you are setting into the the recordId ?? It will be an accountid not opportunity iD .. Please check it .. 


    <aura:attribute name="recordId" type="Id" />
Holly Havelka 10Holly Havelka 10
Raj V, 

I was able to get it to return the values, and below is my updated code:

component:
<aura:component controller="AcctOppsController" implements="flexipage:availableForRecordHome,force:hasRecordId">
    <aura:attribute name="mydata" type="Object"/>
    <aura:attribute name="mycolumns" type="List"/>
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="currentRecordId" type="Id" />
    <aura:handler name="init" value="{!this }" action="{! c.init }"/>
    <div style="height: 300px">
        <lightning:datatable
                keyField="id"
                data="{! v.mydata }"
                columns="{! v.mycolumns }"
                hideCheckboxColumn="true"/>
    </div>
</aura:component>
controller:
({
    init: function (cmp, event, helper) {
        cmp.set('v.mycolumns', [
            {label: 'Opportunity Name', fieldName: 'Opportunity.Name', type: 'text'},
            {label: 'Contact Name', fieldName: 'Contact_Name', type: 'text'},
            {label: 'Role', fieldName: 'Role', sortable: 'true', type: 'text'},
            {label: 'Amount', fieldName: 'Amount', type: 'currency'}
        ]);
        
        helper.fetchData(cmp, event);              
    }
})
helper:
({
    fetchData : function(cmp) {
        var recordId =  cmp.get("v.recordId");
        var action = cmp.get("c.getOpps");
        debugger;
        action.setParams({ currentRecordId : cmp.get("v.recordId") });
        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);
    }
})
apex:
public with sharing class AcctOppsController{
@AuraEnabled
public String currentRecordId {get;set;}

    public AcctOppsController(ApexPages.StandardController controller) {
        currentRecordId  = ApexPages.CurrentPage().getparameters().get('id');
    }
    public AcctOppsController(){
    }
    
    @AuraEnabled    
    public static List<OpportunityContactRole> getOpps(Id currentRecordId) {
        system.debug(currentRecordId);
        List<ID> oppresults1 = new List<ID>();
        List<OpportunityContactRole> oppresults = [SELECT Contact.name, Role, OpportunityId, Opportunity.Amount, Opportunity.StageName, Opportunity.Type FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId];
        system.debug(oppresults);
        return oppresults;
        }
}

The problem I am now facing is that 'Contact.name' 'Opportunity.Name' is not getting values from the parent record.  
Any thoughts on pulling in the parent values vs. just the ids for parent fields?



 
Raj VakatiRaj Vakati
Change your code as below 
 
({
    doInit: function (cmp, event, helper) {
        cmp.set('v.mycolumns', [
            {label: 'Opportunity Name', fieldName: 'OpportunityId', type: 'text'},
            {label: 'Contact Name', fieldName: 'ContactId', type: 'text'},
            {label: 'Role', fieldName: 'Role', type: 'text'},
            {label: 'Amount', fieldName: 'Amount', type: 'currency'}
        ]);
        
        var fetchData = {
            opportunityId: "opportunityId",
            contact : "Contact.Name",
            role : "Role",
            amount : "Opportunity.Amount"
        };
        
        helper.fetchData(cmp);
    }
})
 
({
    fetchData : function(cmp) {
        var recordId =  cmp.get("v.recordId");
        var action = cmp.get("c.getOpps");
        action.setParams({ "currentRecordId" : recordId });
        action.setCallback(this, $A.getCallback(function (response) {
            var state = response.getState();
            if (state ==="SUCCESS") {
                console.log(response.getReturnValue());
                cmp.set("v.mydata", response.getReturnValue());
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
            }
        }
                                               ));
        $A.enqueueAction(action);
    }
})

 
Holly Havelka 10Holly Havelka 10
It's still just pulling in the ids vs. actual values:

Component
Alain CabonAlain Cabon
Hi,

With a new object :  let ob = {};   it is simpler.
 
({
    readData : function(cmp) {
        var recordId =  cmp.get("v.recordId");
        var action = cmp.get("c.myOpps");
        action.setParams({ "currentRecordId" : recordId });
        action.setCallback(this, $A.getCallback(function (response) {
            var state = response.getState();
            var res = [];
            if (state ==="SUCCESS") {
                var resp = response.getReturnValue();
                console.log(resp);
                resp.forEach(function(item, index, array) {                
                    let ob = {};
                    ob.opportunityName = item.Opportunity.Name;
                    ob.contact = item.Contact.Name;
                    ob.role = item.Role;
                    ob.amount = item.Opportunity.Amount;
                    res.push(ob);
                });
                cmp.set('v.mydata',res);
                
            } else if (state === "ERROR") {
                var errors = response.getError();
                console.error(errors);
            }
        }
                                               ));
        $A.enqueueAction(action);
    },
})

({
    doInit: function (cmp, event, helper) {
        cmp.set('v.mycolumns', [
            {label: 'Opportunity Name', fieldName: 'opportunityName', type: 'text'},
            {label: 'Contact Name', fieldName: 'contact', type: 'text'},
            {label: 'Role', fieldName: 'role', type: 'text'},
            {label: 'Amount', fieldName: 'amount', type: 'currency'}
        ]);
         
        helper.readData(cmp);
    }
})

public with sharing class AcctOppsController{
    
    @AuraEnabled    
    public static List<Object> myOpps(String currentRecordId) {
        List<OpportunityContactRole> oppresults = [SELECT Contact.name, Role, OpportunityId, Opportunity.Amount,Opportunity.Name, Opportunity.StageName, Opportunity.Type FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId]; 
        return oppresults;
    }
}


<aura:attribute name="mydata" type="Object[]"/> : generic array of objects
 
<aura:component controller="AcctOppsController" implements="flexipage:availableForRecordHome,force:hasRecordId">
    <aura:attribute name="mydata" type="Object[]"/>
    <aura:attribute name="mycolumns" type="List"/>
    <aura:attribute name="recordId" type="Id" />
    <aura:attribute name="currentRecordId" type="Id" />
    <aura:handler name="init" value="{!this }" action="{! c.doInit }"/>
    <div style="height: 300px">
        <lightning:datatable
                keyField="id"
                data="{! v.mydata }"
                columns="{! v.mycolumns }"
                hideCheckboxColumn="true"/>
    </div>
</aura:component>

 
This was selected as the best answer
Holly Havelka 10Holly Havelka 10
Alain!  Thanks again, this worked perfectly!
Alain CabonAlain Cabon
Hi Holly,
 
var fetchData = {
            opportunityId: "opportunityId",
            contact : "Contact.Name",
            role : "Role",
            amount : "Opportunity.Amount"
        };

This object (fetchData) was the pattern object for the conversion probably.  We have used "ob" instead.
Probably to avoid the hard coding field by field (  ob.opportunityName = item.Opportunity.Name; )

Using the object fetchData, it is easy for direct field (OpportunityId and Role) to have a generic coding but for the indirect fields (  Contact.Name and Opportunity.Amount ), it is more difficult and I haven't been able to solve this last problem.

By the way, the helper of Salesforce for the official documentation is bizarre:
https://developer.salesforce.com/docs/component-library/bundle/lightning:datatable/example

fakerLib = this.mockdataLibrary.getFakerLib();

Only works for their tests or just an error?

The fetching of data by scope is perhaps manage by this strange "FakerLib". It is the first time that I have seen something like that.

Whatever it might be, this sample is quite useless for someone who wants an example that works with data on the server side.
David Roberts 4David Roberts 4
I've just worked around the whole "fakerLib = this.mockdataLibrary.getFakerLib();" issue in the "Example"
https://developer.salesforce.com/docs/component-library/bundle/lightning:datatable/example
so that I could fully explore what a DataTable has to offer.
I've used much of the advice above to produce the solution included below.
I haven't completed all the buttons for adding records. Feel free to continue and provide a revision.
I struggled and gave in on the field that is a 'Location' type object.
If anyone can suggest a working syntax I'd be grateful. I ended up treating it as text.
I've used a Promise but am still puzzled as to why my result isn't refreshed in my diagnostic <Div> whereas my message, set in the same place, is reported correctly. Again, any answers appreciated.
So here is my solution:

DataTableInAction.cmp
<aura:component controller="DataTableInActionController" implements="flexipage:availableForAllPageTypes,force:hasRecordId">
    
    <!-- https://developer.salesforce.com/docs/component-library/bundle/lightning:datatable/example#lightningcomponentdemo:exampleDatatableInAction
			has the mockdata.
			way round is shown here...
			https://developer.salesforce.com/forums/?id=9060G0000005m4vQAA -->     
        
    <!-- attributes -->
    <aura:attribute name="columns" type="List" default="[]"/>
    <aura:attribute name="data" type="List" default="[]"/>
    <aura:attribute name="keyField" type="String" default="id"/>
    <aura:attribute name="hideCheckboxColumn" type="Boolean" default="false"/>
    <aura:attribute name="selectedRowsCount" type="Integer" default="0"/>
    <aura:attribute name="isLoading" type="Boolean" default="false"/>
    <aura:attribute name="resizeColumnDisabled" type="Boolean" default="false"/>
    <aura:attribute name="minColumnWidth" type="Integer"/>
    <aura:attribute name="maxColumnWidth" type="Integer"/>
    <aura:attribute name="sortedBy" type="String"/>
    <aura:attribute name="sortedDirection" type="String"/>
    <aura:attribute name="defaultSortDirection" type="String"/>
    <aura:attribute name="showRowNumberColumn" type="Boolean" default="false"/>
    <aura:attribute name="rowNumberOffset" type="Integer" default="0"/>
    <aura:attribute name="initialRows" type="Integer" default="5"/>
    <aura:attribute name="rowsToAdd" type="Integer" default="5"/>
    <aura:attribute name="dataTableSchema" type="Object"/>
    
    <!-- from https://developer.salesforce.com/forums/?id=9060G0000005m4vQAA --> -->
    <aura:attribute name="mydata" type="Object"/>
    <aura:attribute name="mycolumns" type="List"/>
    <!--<aura:attribute name="recordId" type="Id" />-->
    <aura:attribute name="currentRecordId" type="Id" />
     <aura:attribute name="message" type="String" />

    <!-- Imports -->
    <!--<aura:import library="lightningcomponentdemo:mockdataFaker" property="mockdataLibrary"/>-->

    <!-- handlers-->
    <aura:handler name="init" value="{! this }" action="{! c.init }"/>


    <div class="slds-is-relative">
        <!-- toolbox -->
        <div class="slds-m-vertical_small">
            <h1 class="slds-m-vertical_small">Total Rows: {! v.data.length }</h1>
            <h1 class="slds-m-vertical_small">Selected Rows: {! v.selectedRowsCount }</h1>

            <div class="input-group">
                <lightning:input type="toggle" label="Disable Column Resize" name="inputResizable" checked="{! v.resizeColumnDisabled }"/>
            </div>

            <div class="input-group">
                <lightning:input type="toggle" label="Hide Checkbox Column" name="inputSelectable" checked="{! v.hideCheckboxColumn }"/>
            </div>

            <div class="input-group">
                <lightning:input type="toggle" label="Show Row Number Column" name="inputRowNumber" checked="{! v.showRowNumberColumn }"/>
                <lightning:input type="number" label="Row number offset" name="inputRowNumberOffset" value="{! v.rowNumberOffset }"/>
            </div>

            <div class="input-group">
                <lightning:input type="number" label="Rows To Add" name="inputRowsToAdd" value="{! v.rowsToAdd }"/>
                <lightning:button label="Add to the beginning" onclick="{! c.addRowsToBeginning }"/>
                <lightning:button label="Add to the end" onclick="{! c.addRowsToEnd }"/>
                <lightning:button label="Clear Rows" onclick="{! c.clearRows }"/>
            </div>

            <div class="input-group">
                <lightning:button label="Reset Columns" onclick="{! c.resetColumns }"/>
            </div>
        </div>
        
        
        <div>myData={!v.mydata}~~~</div>
        <div>message={!v.message}~~~</div>

        <!-- the container element determine the height of the datatable -->
        <div style="height: 300px">
            <lightning:datatable
                columns="{! v.columns }"
                data="{! v.mydata }"
                keyField="{! v.keyField }"
                hideCheckboxColumn="{! v.hideCheckboxColumn }"
                resizeColumnDisabled="{! v.resizeColumnDisabled }"
                minColumnWidth="{! v.minColumnWidth }"
                maxColumnWidth="{! v.maxColumnWidth }"
                resizeStep="20"
                sortedBy="{! v.sortedBy }"
                sortedDirection="{! v.sortedDirection }"
                defaultSortDirection="{! v.defaultSortDirection }"
                showRowNumberColumn="{! v.showRowNumberColumn }"
                rowNumberOffset="{! v.rowNumberOffset }"
                onrowselection="{! c.updateSelectedText }"
                onrowaction="{! c.handleRowAction }"
                onresize="{! c.storeColumnWidths }"
                onsort="{! c.updateColumnSorting }"
                />
        </div>
        
        <aura:if isTrue="{! v.isLoading }">
            <lightning:spinner alternativeText="Loading"/>
        </aura:if>
    </div>
</aura:component>

DataTableInActionController.js
({ //DataTableInActionController.js
    init: function (cmp, event, helper) {
        cmp.set('v.columns', helper.getColumnDefinitions());
        
        
        //https://developers.google.com/web/fundamentals/getting-started/primers/promises.
        //https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_promises.htm
        console.log('initial rows = ' + cmp.get('v.initialRows'));
        var dataPromise = helper.fetchData(cmp, cmp.get('v.initialRows'));
        
        
        // cmp.set('v.dataTableSchema', fetchData);
        dataPromise.then($A.getCallback(function(results) {
            //console.log('results');console.log(results);
            cmp.set('v.mydata', results);
            cmp.set("v.message", 'mydata set in init on startup');
        }));
        
        
        
    },//init
    
    updateSelectedText: function (cmp, event) {
        var selectedRows = event.getParam('selectedRows');
        cmp.set('v.selectedRowsCount', selectedRows.length);
    },
    storeColumnWidths: function (cmp, event, helper) {
        helper.storeColumnWidths(event.getParam('columnWidths'));
    },
    addRowsToBeginning: function (cmp, event, helper) {
        var fetchData = cmp.get('v.dataTableSchema'),
            dataPromise = helper.fetchData(cmp, cmp.get('v.rowsToAdd'));


        dataPromise.then($A.getCallback(function (data) {
            var currentData = cmp.get('v.data');
            var newData = data.concat(currentData);
            cmp.set('v.data', newData);
        }));
    },
    addRowsToEnd: function (cmp, event, helper) {
        var fetchData = cmp.get('v.dataTableSchema'),
            dataPromise = helper.fetchData(cmp, cmp.get('v.rowsToAdd'));

        dataPromise.then($A.getCallback(function (data) {
            var currentData = cmp.get('v.data');
            var newData = currentData.concat(data);
            cmp.set('v.data', newData);
        }));
    },
    clearRows: function (cmp) {
        cmp.set('v.data', []);
    },
    resetColumns: function (cmp, event, helper) {
        helper.resetLocalStorage();
        cmp.set('v.columns', helper.getColumnDefinitions());
    },
    updateColumnSorting: function (cmp, event, helper) {
        cmp.set('v.isLoading', true);
        // We use the setTimeout method here to simulate the async
        // process of the sorting data, so that user will see the
        // spinner loading when the data is being sorted.
        setTimeout($A.getCallback(function() {
            var fieldName = event.getParam('fieldName');
            var sortDirection = event.getParam('sortDirection');
            cmp.set("v.sortedBy", fieldName);
            cmp.set("v.sortedDirection", sortDirection);
            helper.sortData(cmp, fieldName, sortDirection);
            cmp.set('v.isLoading', false);
        }), 0);
    },
    handleRowAction: function (cmp, event, helper) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        switch (action.name) {
            case 'view_details':
                helper.showRowDetails(row);
                break;
            case 'edit_status':
                helper.editRowStatus(cmp, row, action);
                break;
            default:
                helper.showRowDetails(row);
                break;
        }
    }
}) //DataTableInActionController.js

DataControllerInActionHelper.js
({//DataTableInActionHelper.js
    //https://developer.salesforce.com/forums/?id=9060G0000005m4vQAA
    
    getColumnDefinitions: function () {
        var columnsWidths = this.getColumnWidths();
        var columns = [
            {label: 'Opportunity name', fieldName: 'opportunityName', type: 'text', sortable: true, iconName: 'standard:opportunity'},
            {label: 'Account name', fieldName: 'accountName', type: 'text', sortable: true, iconName: 'standard:account'},
            {label: 'Close date', fieldName: 'closeDate', type: 'date', sortable: true, cellAttributes: { iconName: 'utility:event', iconAlternativeText: 'Close Date'  }},
            {label: 'Confidence', fieldName: 'confidence', type: 'percent', sortable: true, cellAttributes:
            	{iconName: { fieldName: 'confidenceDeltaIcon' }, iconLabel: { fieldName: 'confidenceDelta' }, iconPosition: 'right', iconAlternativeText: 'Percentage Confidence' }},
            {label: 'View', type: 'button', initialWidth: 135, typeAttributes: { label: 'View Details', name: 'view_details', title: 'Click to View Details'}},
            {label: 'Amount', fieldName: 'amount', type: 'currency', typeAttributes: { currencyCode: 'EUR'}, sortable: true},
            {label: 'Action', type: 'button', initialWidth: 150, typeAttributes:
            {label: { fieldName: 'actionLabel'}, title: 'Click to Edit', name: 'edit_status', iconName: 'utility:edit', disabled: {fieldName: 'actionDisabled'}, class: 'btn_next'}},
            {label: 'Contact Email', fieldName: 'contact', type: 'email'},
            {label: 'Contact Phone', fieldName: 'phone', type: 'phone'},
            {label: 'Website', fieldName: 'website', type: 'url', typeAttributes: { label: { fieldName: 'website' }, target: '_blank', tooltip: 'Click to visit website' }},
            {label: 'Address', fieldName: 'address', type: 'text', typeAttributes: { target: '_blank'}}
        ];
        //re: Address: can't get type location to work so used text. Any suggestions?

        if (columnsWidths.length === columns.length) {
            return columns.map(function (col, index) {
                return Object.assign(col, { initialWidth: columnsWidths[index] });
            });
        }
        return columns;
    },
    
    fetchData : function(cmp, numberOfRecords) {
        
        var datapromise;
        
        //console.log('num recs = '+numberOfRecords);
        var recordId =  cmp.get("v.recordId");
        var numRecs = numberOfRecords;
        if (recordId === undefined){recordId = '0018E00000VmZfDQAV';} //while debugging
        
        
        datapromise = new Promise(function(resolve,reject){
            var action = cmp.get("c.getData");
            action.setParams({ "currentRecordId" : recordId, "recLimit" : numRecs });
            
            action.setCallback(this, $A.getCallback(function (response) {
                var state = response.getState();
                if (state ==="SUCCESS") {
                    //console.log(response.getReturnValue());
                    //cmp.set("v.mydata", response.getReturnValue());
                    //cmp.set("v.message", 'response.getReturnValue()');
                    //let objects = response.getReturnValue();
                    //resolve(helper.parseObjects(objects));
                    resolve(response.getReturnValue());
                    //
                    //dkr
                    //this is where we're at.
                } else if (state === "ERROR") {
                    var errors = response.getError();
                    console.error(errors);
                }
            }
                                                   ));
            $A.enqueueAction(action);
        });//datapromise
        
        return datapromise;
    },

                     
    
    sortData: function (cmp, fieldName, sortDirection) {
        var data = cmp.get("v.data");
        var reverse = sortDirection !== 'asc';
        
        data = Object.assign([],
                             data.sort(this.sortBy(fieldName, reverse ? -1 : 1))
                            );
        cmp.set("v.data", data);
    },
    sortBy: function (field, reverse, primer) {
        var key = primer
        ? function(x) {
            return primer(x[field]);
        }
        : function(x) {
            return x[field];
        };
        
        return function (a, b) {
            var A = key(a);
            var B = key(b);
            return reverse * ((A > B) - (B > A));
        };
    },
    storeColumnWidths: function (widths) {
        localStorage.setItem('datatable-in-action', JSON.stringify(widths));
    },
    resetLocalStorage: function () {
        localStorage.setItem('datatable-in-action', null);
    },
    getColumnWidths: function () {
        var widths = localStorage.getItem('datatable-in-action');

        try {
            widths = JSON.parse(widths);
        } catch(e) {
            return [];
        }
        return Array.isArray(widths) ? widths : [];
    },
    editRowStatus: function (cmp, row) {
        var data = cmp.get('v.data');
        data = data.map(function(rowData) {
            if (rowData.id === row.id) {
                switch(row.actionLabel) {
                    case 'Approve':
                        rowData.actionLabel = 'Complete';
                        break;
                    case 'Complete':
                        rowData.actionLabel = 'Close';
                        break;
                    case 'Close':
                        rowData.actionLabel = 'Closed';
                        rowData.actionDisabled = true;
                        break;
                    default:
                        break;
                }
            }
            return rowData;
        });
        cmp.set("v.data", data);
    },
    showRowDetails : function(row) {
        // eslint-disable-next-line no-alert
        alert("Showing opportunity " + row.opportunityName + " closing on " + row.closeDate);
    }
});//DataTableInActionHelper.js
class DataTableInActionController
public with sharing class DataTableInActionController{
    @AuraEnabled
    public String currentRecordId {get;set;}
    
    public DataTableInActionController(ApexPages.StandardController controller) {
        currentRecordId  = ApexPages.CurrentPage().getparameters().get('id');
    }
    public DataTableInActionController(){
    }
    
    
    @AuraEnabled    
    public static List<Map<String, Object>> getData( Id currentRecordId, String recLimit) {
        
        //, String recLimit
        //currentRecordId = '0018E00000VmZfDQAV'; //force while debugging
        List<Map<String, Object>> ret = new List<Map<String, Object>>();
        if (recLimit == NULL) {recLimit= '5';} //belt and braces!
        Integer iLimit = Integer.valueOf(recLimit);
        List<OpportunityContactRole> contacts = [SELECT Contact.Name, Contact.phone, Contact.Email, Contact.Account.Name,
                                                 Contact.Account.BillingAddress, Contact.Account.Website,
                                                 Opportunity.Name, Opportunity.CloseDate, OpportunityId, Opportunity.Amount,
                                                 Opportunity.StageName, Opportunity.Type
                                                 FROM OpportunityContactRole WHERE contact.accountid=:currentRecordId
                                                 LIMIT :iLimit];
        
        
        
        //Location loc;
        for (OpportunityContactRole c: contacts) {
            Map<String, Object> ocritem = new Map<String, Object>();
            ocritem.put('opportunityName', c.Opportunity.Name);
            ocritem.put('accountName', c.Contact.Account.Name);
            ocritem.put('name', c.Opportunity.Name);
            ocritem.put('closeDate', c.Opportunity.CloseDate);
            ocritem.put('amount', c.Opportunity.Amount);
            ocritem.put('contact', c.Contact.Email);
            ocritem.put('confidence', Math.random());
            ocritem.put('phone', c.Contact.phone);
            ocritem.put('website', c.Contact.Account.Website);
            
            if (Math.random()>0.25){
                ocritem.put('actionLabel', 'Approve'); 
            }else
                if (Math.random()>0.5){
                    ocritem.put('actionLabel', 'Complete'); 
                }
            else
                if (Math.random()>0.75){
                    ocritem.put('actionLabel', 'Close'); 
                }
            else{ocritem.put('actionLabel', 'Closed');}
            
            
            
            if (Math.random()>0.5){
                ocritem.put('confidenceDeltaIcon', 'utility:up'); 
            }else{ocritem.put('confidenceDeltaIcon', 'utility:down');}
            
            //Location loc = Location.newInstance(c.Contact.Account.BillingAddress.getLatitude(),c.Contact.Account.BillingAddress.getLongitude());
            //ocritem.put('address', loc); //why doesn't this work?
            ocritem.put('address', '57,-122'); //done as text because can't get location to work. Any ideas????
            
            ret.add(ocritem);
        }//next
        
        
        
        /* an alternative way to do the query...      
        List<Map<String, Object>> ret = new List<Map<String, Object>>();
        List<Opportunity> opps = [SELECT Id, Name, account.name, CloseDate, Amount,
        (SELECT Contact.Name,Contact.Email, ContactId, Contact.phone FROM OpportunityContactRoles)
        FROM Opportunity
        WHERE accountid=:currentRecordId AND ContactId != NULL];
        for (Opportunity c: opps) {
            Map<String, Object> item = new Map<String, Object>();
            
            item.put('opportunityName', c.Name);
            item.put('accountName', c.Account.Name);
            item.put('closeDate', c.CloseDate);
            item.put('amount', c.Amount);
            
            integer count = 1;
            for (OpportunityContactRole ocr : c.OpportunityContactRoles){
                if (count==1){
                item.put('contact', ocr.Contact.Email);
                item.put('phone', ocr.Contact.phone);
                count = 2;
                }
            }
            
                     
            ret.add(item);
        }//next
        
        */     
        
        return ret;
    }//getData
    
}//class DataTableInActionController

Hope this helps and I look forward to answers to my outstanding queries and lack of knowledge.

I write this as the world is being attacked by the Corona virus. As Marc Benioff says, "..we’re all connected like never before. We’re called upon to be our best selves, with patience, understanding and compassion".
​​​​​​​
I wish that you stay safe and well.



 
{tushar-sharma}{tushar-sharma}
You can find ready to use lightning data table component example here: This component does support multiple field types, Inline editing, Row-level action with lazy loading. This is a complete reusable component.

 https://newstechnologystuff.com/2019/01/01/lightning-datatable-lazy-loading-with-inline-editing-and-actions/