[SalesForce] Search functionality in lightning:datatable

Is there a way to search across all rows/columns in a lightning:datatable to narrow down the list, just like jQuery DataTables? If so, how do implement that?

Can I somehow do it all on the client-side to make it super fast (some sort of DOM manipulation)

I'm having some issues with the lightning:datatable not updating. I have attached my code below, maybe you can help me spot the issue:

Markup:

<aura:component extends="c:Base" controller="AccountBrowserForm">
<aura:handler name="init" value="{! this }" action="{! c.doInit }"/>
<aura:attribute name="selectedAccountId" type="Id" access="public" />
<aura:attribute name="accounts" type="Object"/>
<aura:attribute name="mycolumns" type="List"/>
<aura:attribute name="sortedBy" type="String" />
<aura:attribute name="sortedDirection" type="Boolean" default="true" />

<aura:registerEvent name="AccountFilterChange" type="c:AccountFilterChange" />

<aura:attribute name="backingdata" type="List" />
<aura:attribute name="filter" type="String" />
<lightning:input type="text" onchange="{!c.filter}" value="{!v.filter}" label="Filter" />

<lightning:datatable aura:id="lightningTable"
                     data="{! v.accounts }"
                     columns="{! v.mycolumns }"
                     keyField="Id"
                     onrowaction="{! c.handleRowAction }"
                     onsort="{!c.updateColumnSorting}"
                     sortedBy="{!v.sortedBy}"
                     sortedDirection="{!v.sortedDirection}"
                     hideCheckboxColumn="true"
/>

Controller:

doInit: function (component, event, helper) {
    helper.callServer(component, "c.getAccounts", $A.getCallback(function (response) {
        component.set("v.accounts", response);
        component.set("v.backingdata", response);
        console.log(component.get("v.backingdata") + " /// BACKING DATA INSIDE ")
        console.log(component.get("v.accounts") + " // ACCOUTN DATA INSIDE")

        component.set('v.mycolumns', [{
                type: "button",
                typeAttributes: {
                    label: '',
                    iconName: 'utility:add',
                    name: 'selectRecord',
                    title: 'selectRecord',
                    disabled: false,
                    value: 'edit',
                    variant: {
                        fieldName: 'variantValue'
                    },
                }
            },
            {label: 'Name', fieldName: 'Name', type: 'text', sortable: true},
            {label: 'Street', fieldName: 'BillingStreet', type: 'text', sortable: true},
            {label: 'Postal Code', fieldName: 'BillingPostalCode', type: 'text', sortable: true},
            {label: 'City', fieldName: 'BillingCity', type: 'text', sortable: true},
            {label: 'Country', fieldName: 'BillingCountry', type: 'text', sortable: true},
        ]);

        console.log(component.get("v.backingdata") + " *** BACKING DATA OUTSIDE *** ")
        console.log(component.get("v.accounts") + " *** ACCOUNT DATA OUTSIDE *** ")
    }))
},

filter: function (component, event, helper) {
    var data = component.get("v.backingdata"),
        term = component.get("v.filter"),
        results = data,
        regex;
    try {
        regex = new RegExp(term, "i");
        // filter checks each row, constructs new array where function returns true
        results = data.filter(row => regex.test(row.name) || regex.test(row.age.toString()));
    } catch (e) {
        // invalid regex, use full list
    }
    component.set("v.accounts", results);
},

For some reason the filter function is completely unresponsive. Also, does it matter that I'm populating the component with a response from a callback function. @sfdcfox, I noticed in your example that you simple created an array with dummy data. My "myaccounts" attribute is of type object

Best Answer

Searching with a data table is the same as search with an aura:iteration. You still need to have a backing attribute to store the extra data, etc. Here's an example of what that looks like in action.


<aura:application extends="force:slds">
    <!-- backing data -->
    <aura:attribute name="data" type="List" />

    <!-- data table attributes -->
    <aura:attribute name="columns" type="List" />
    <aura:attribute name="filteredData" type="List" />

    <!-- filter input -->
    <aura:attribute name="filter" type="String" />

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

    <lightning:input type="text" onchange="{!c.filter}" value="{!v.filter}" label="Filter" />
    <lightning:datatable keyField="name" columns="{!v.columns}" data="{!v.filteredData}" />
</aura:application>

({
    init: function(component, event, helper) {
        var sample = [];
        // Create 10 random people
        [...Array(10).keys()].forEach(v=>sample.push({name:"Person "+(v+1), age: Math.floor(Math.random()*80)}));
        // initialize data
        component.set("v.columns", [{type:"text",label:"Name",fieldName:"name"},{type:"number",label:"Age",fieldName:"age"}]);
        component.set("v.data", sample);
        component.set("v.filteredData", sample);
    },
    filter: function(component, event, helper) {
        var data = component.get("v.data"),
            term = component.get("v.filter"),
            results = data, regex;
        try {
            regex = new RegExp(term, "i");
            // filter checks each row, constructs new array where function returns true
            results = data.filter(row=>regex.test(row.name) || regex.test(row.age.toString()));
        } catch(e) {
            // invalid regex, use full list
        }
        component.set("v.filteredData", results);
    }
})

Hopefully, as you can see, it's mostly a matter of of simply changing the bits using aura:iteration to use lightning:datatable instead, as most of the rest of the logic is nearly identical, such as this example (my own Gist).