[SalesForce] LWC (Lightning Web Component) Data-Table Header Level Actions

I have a LWC Data-Table in which I am displaying Activity currently. I have three columns in the data table: Subject, Comments, Activity Date. I would like to add a header level action to the Subject column header to allow for filtering if the subject contains either 'call' or 'email'. This would allow the user to "filter" the results of the data table to those either containing call or email. I would also like to maintain the ability for the user to view all Activity.

Here is my HTML:

        <lightning-datatable 
            key-field="sId" 
            hide-checkbox-column 
            data={activityList} 
            columns={columns}
            min-column-width=10px
            onheaderaction={handleHeaderAction}>
        </lightning-datatable>

Here is my JS:

    export default class LeadActivity extends LightningElement {

        @api recordId;
        @api objectApiName;
        @track activityList;
        @track notesList;
        @track error;
        @track activeFilter = 'All';

        columns = 
        [
            { 
                label: 'Subject',
                fieldName: 'sActivitySubject', 
                type: 'text', 
                initialWidth: 250,
                actions: [
                    {label: 'All', checked: true, name: 'All'},
                    {label: 'Calls', checked: false, name: 'Calls'},
                    {label: 'Emails', checked: false, name: 'Emails'}
                ]
            },
            { 
                label: 'Comments', 
                fieldName: 'sActivityComments', 
                type: 'text',
            },
            { 
                label: 'Activity Date', 
                fieldName: 'sActivityDate', 
                type: 'text', 
                initialWidth: 150
            },
        ];

        @wire(getActivityAndNotes, {leadId: '$recordId'})
        data({error,data}) {
            if(data){

                console.log('Log Data:');
                console.log(data);
                console.log(JSON.stringify(data, null, '\t'));
                console.log('Log Data.Activities:');
                console.log(data.Activities);
                console.log(JSON.stringify(data.Activities, null, '\t'));
                console.log('Log Data.Notes:');
                console.log(data.Notes);
                console.log(JSON.stringify(data.Notes, null, '\t'));            

                this.activityList = data.Activities;
                this.notesList = data.Notes;

            } else if(error){

                console.log('Activity Error');
                console.log(error);
                this.error = error;

            } else{

                console.log('Sorry Nothing Happened');

            }
        }

        handleHeaderAction(event) {

            // Retrieves the name of the selected filter
            const actionName = event.detail.action.name;
            const col = event.detail.columnDefinition;
            const columns = this.columns;
            const activeFilter = this.activeFilter;

            if(actionName !== activeFilter){
                var idx = columns.indexOf(col);
                var actions = columns[idx].actions;
                actions.forEach((action) => {
                    action.checked = action.name === actionName;
                })
            }
            this.activeFilter = actionName;
            this.updateActivity();
            this.columns = columns;

        }

        updateActivity(){
            const rows = this.activityList;
            const filter = this.activeFilter;
            let filteredRows = rows;
            if (filter !== 'all') {
                filteredRows = rows.filter(function (row) {
                    if(activeFilter.includes('email')){
                        return activeFilter === 'Email'
                    } else return activeFilter === 'Calls'
                });
            }
            this.activityList = filteredRows;
        }

    }

I am getting a general error when attempting to filter the column. In the second function I am attempting to filter for includes email. I am not sure if that is possible…

I have done some additional logging and it looks like the idx is getting a value of -1. So I am not sure why that is happening.

Updates
I have added some additional logging to the code to see the values and results of some of the actions. Below you will see the new JS code, the JS console logging, & the error I am receiving from Salesforce.

JS:

  handleHeaderAction(event) {

        console.log(1);

        // Retrieves the name of the selected filter
        const actionName = event.detail.action.name;
        const col = event.detail.columnDefinition;
        const columns = this.columns;
        const activeFilter = this.activeFilter;

        console.log('event.detail: ', event.detail);
        console.log('ActionName: ', actionName);
        console.log('col: ', col);
        console.log('columns: ', columns);
        console.log('activeFilter: ', activeFilter);

        console.log(2);

        if(actionName !== activeFilter){

            console.log(2.1);

            var idx = columns.indexOf(col);

            console.log(2.11);

            console.log('idx:', idx);
            console.log('columns: ',columns);
            console.log('columns[idx]: ',columns[idx]);

            var actions = columns[0].actions;

            console.log('idx: ', idx);
            console.log('actions: ', actions);

            console.log(2.2);

            actions.forEach((action) => {
                console.log('checked: ', action.checked);
                console.log('Name: ', action.name);
                console.log('actionName: ', actionName);
                action.checked = action.name === actionName;
            })
        }

        console.log(3);

        this.activeFilter = actionName;
        this.updateActivity();
        this.columns = columns;

        console.log(4);

    }

    updateActivity(){

        console.log(5);

        const rows = this.activityList;
        const filter = this.activeFilter;
        let filteredRows = rows;

        console.log('rows: ', rows);
        console.log('filter: ', filter);
        console.log('filteredRows: ', filteredRows);

        console.log(6);

        if (filter !== 'All') {

            filteredRows = rows.filter(function (row) {

                console.log(filteredRows);
                console.log(activeFilter);

                if(activeFilter.includes('email')){
                    return activeFilter === 'Email'
                } else return activeFilter === 'Calls'

            });

        }

        this.activityList = filteredRows;
    }

Logging:

enter image description here

Error:
enter image description here

Questions:

  1. Am I able to filter a row within a LWC Data-Table for data that includes X, in this case I would like to filter of the Subject includes either a call or email.
  2. Is the way I am doing it the most appropriate way? Or even accurate?

Best Answer

handleHeaderAction(event) method should have the following code. Note the code change inside the IF block.

handleHeaderAction(event) {
    const actionName = event.detail.action.name;
    const colName = event.detail.columnDefinition.fieldName;
    const columns = this.columns;
    const activeFilter = this.activeFilter;    
    if(actionName !== activeFilter){
        columns.map((col) => {
            if(col.fieldName === colName){
                var actions = col.actions;
                actions.forEach((action) => {
                    action.checked = action.name === actionName;
                });
            }
        });    
        this.activeFilter = actionName;
        this.updateActivity(colName);
        this.columns = columns;
    }
}

updateActivity method should be like this. Note the input parameter and the code change inside the IF block. Also, you will need to improve upon this code to handle scenarios when the user clicks on the header and filters to different values. This code will handle the filter scenario only once (i.e., If user selects the filter Call or Email, the datatable will be filtered. However, if the user switches back to All filter or any other filter without page refresh, no rows will be displayed)

updateActivity(columnName){
    const rows = this.activityList;
    const filter = this.activeFilter;
    let filteredRows = rows;
    if(filter !== 'all'){
        filteredRows = rows.filter((row) => {
            if(row[columnName] !== '' && row[columnName] !== undefined){
                return row[columnName].toUpperCase() === filter.toUpperCase();
            }                
        });            
    }
    this.activityList= filteredRows;  
}

Observations: Looking at the code and the JS console log, I see few things done incorrectly in your JS code.

  1. In the handleHeaderAction(event) method, the following line of code is not fetching the column index value. In the console log, the value shows up as -1 (implies that the column on which user took action is not found).

    var idx = columns.indexOf(col);

    The indexOf method works for single dimensional arrays, but in this code we are working on a multi-dimensional array. All of the code after this line would either fail or not produce the required response.

  2. In the updateActivity method, the following piece of code logic doesn't make sense to me. Not sure if you just wrote this as a sample.

    if(activeFilter.includes('email')){ return activeFilter === 'Email' } else return activeFilter === 'Calls'

    The variable activeFilter is not declared in this method and instead it should have been the variable filter. I suppose this code was written with reference from the LWC component reference. Not all the code from this reference would work as-is and is given only as a sample.

Keep a check on the following while writing LWC code. While these points seem trivial, bear in mind that the lightning platform minifies and converts your code into a slightly different structure before rendering to the browser. A missing semi-colon or a paranthesis, you change the code logic causing errors.

  1. If you use functional javascript like map, forEach etc., do not omit ';' at the end. For example,

    SomeArray.forEach((element) => { code block });

  2. If you have IF-ELSE statement with only one like under IF or ELSE block, ensure that you use curly braces for that like and also, do not omit ';'' on that line. For example,

    if(condition){ return true } else return false

    should be written as

    if(condition){ return true; } else{ return false; }

Related Topic