Custom List View in LWC

Hello!
Maybe you wondered how you can provide to your users a Salesforce List View look and feel. It is nothing hard with Lightning Web Components which allows you to build pretty similar functionality with not much effort. What’s important Custom List View in LWC is pretty similar to built-in Salesforce feature and components build same way as Salesforce standard ones are more intuitive for it’s users.

How to build custom List View in LWC

First thing which you need to include in your solution is lightning-datatable which is really universal component. It can be used in really many cases, but we will use it do display data based on user filters. This helps us not only view records but in addition edit them, what cannot be done with standard List View. Besides of this we also need to create some html and javascript.

testListView.html

<template>
    <div class="slds-p-around_medium lgc-bg list_view_and_table_container">
        <lightning-tile label="List of Items" title="List of Items" type="media" href="#" class="list_view_container">
            <lightning-icon slot="media" icon-name="standard:orders" size="medium" class="icon_custom_class">
            </lightning-icon>
            <div class="slds-form-element">
                <div class="slds-form-element__control">
                    <div class="slds-combobox_container slds-size_small">
                        <div class={dropdownTriggerClass} aria-expanded="false" aria-haspopup="listbox" role="combobox">
                            <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right"
                                role="none" onclick={handleClickExtend}>
                                <span class="current_filter">{currentFilter}
                                    <lightning-icon
                                        class="slds-button__icon slds-icon-utility-down slds-icon_container forceIcon"
                                        data-data-rendering-service-uid="232" data-aura-rendered-by="371:0"
                                        data-aura-class="forceIcon" icon-name="utility:down" size="x-small">
                                    </lightning-icon>
                                </span>
                            </div>
                            <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid"
                                role="listbox">
                                <ul class="slds-listbox slds-listbox_vertical" role="presentation">
                                    <template for:each={filterOptions} for:item="option">
                                        <li role="presentation" class="slds-listbox__item" key={option.value}>
                                            <div class="slds-media slds-listbox__option slds-listbox__option_plain slds-media_small"
                                                data-filter={option.value} onclick={handleFilterChangeButton}>
                                                <span class="slds-media__figure slds-listbox__option-icon"
                                                    data-filter={option.value}></span>
                                                <span class="slds-media__body" data-filter={option.value}>
                                                    <span class="slds-truncate" title="Option A"
                                                        data-filter={option.value}>{option.label}</span>
                                                </span>
                                            </div>
                                        </li>
                                    </template>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </lightning-tile>
    </div>
    <lightning-card>
        <template if:false={isLoaded}>
            <div class="spinner_class_div">
                <lightning-spinner class="spinner_class" alternative-text="Loading" variant="brand">
                </lightning-spinner>
            </div>
        </template>
        <lightning-datatable key-field="Id" data={itemsForCurrentView} columns={columns}>
        </lightning-datatable>
    </lightning-card>
</template>

Lines 9-12: We use dropdownTriggerClass css class which is conditionaly defined in js file, based on status of dropdown button. Next lines handles click on ListView button which is used to change filter of data.
Lines 23-31: template for:each={filterOptions} is used here to iterate over defined list of filters for our list ciew button. Also every element inside iteration contains data-filter={option.value} to indicate which exactly filter has been chosen.
Lines 47-48: Built-in lightning-spinner which obviously notifies user that action is in progress.
Lines 51-52: Here comes lightning-datatable which is the most important part of html structure. It displays proper data based on current filter using itemsForCurrentView which is changed accordingly to every filter.

testListView.js

import { LightningElement, track } from 'lwc';

const columns = [
    { label: 'Item Name', fieldName: 'Name', type: 'text', hideDefaultActions: true },
    { label: 'Item Code', fieldName: 'Code', type: 'text', hideDefaultActions: true },
    { label: 'Price', fieldName: 'Price', type: 'number', hideDefaultActions: true, editable: true },
    { label: 'Priority', fieldName: 'Priority', type: 'text', hideDefaultActions: true },
];

const HIGH_PRIORITY = 'High Priority';
const LOW_PRIORITY = 'Low Priority';
const MEDIUM_PRIORITY = 'Medium Priority';
const ALL_PRIORITY = 'All';

const filterOptions = [
    { value: HIGH_PRIORITY, label: HIGH_PRIORITY },
    { value: LOW_PRIORITY, label: LOW_PRIORITY },
    { value: MEDIUM_PRIORITY, label: MEDIUM_PRIORITY },
    { value: ALL_PRIORITY, label: ALL_PRIORITY },
];

const allItems = [
    { Name: 'test 1', Code: '12323412', Price: 123123, Priority: HIGH_PRIORITY },
    { Name: 'test 2', Code: '12376345', Price: 999999, Priority: LOW_PRIORITY },
    { Name: 'test 3', Code: '89645634', Price: 15, Priority: HIGH_PRIORITY },
    { Name: 'test 4', Code: '64564574', Price: 234, Priority: LOW_PRIORITY },
    { Name: 'test 5', Code: '78456246', Price: 7567, Priority: MEDIUM_PRIORITY },

];

export default class TestListView extends LightningElement {
    @track currentFilter = ALL_PRIORITY;
    @track isExpanded = false;
    @track itemsForCurrentView = allItems;
    @track isLoaded = false;
    allItems = allItems;
    filterOptions = filterOptions;
    columns = columns;

    renderedCallback() {
        this.isLoaded = true;
    }

    get dropdownTriggerClass() {
        if (this.isExpanded) {
            return 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click custom_list_view slds-is-open'
        } else {
            return 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click custom_list_view'
        }
    }

    handleFilterChangeButton(event) {
        this.isLoaded = false;
        let filter = event.target.dataset.filter;
        this.isExpanded = !this.isExpanded;
        if (filter !== this.currentFilter) {
            this.currentFilter = event.target.dataset.filter;
            setTimeout(() => {
                this.handleFilterData(this.currentFilter), 0
            });
        } else {
            this.isLoaded = true;
        }
    }

    handleFilterData(filter) {
        if (filter === ALL_PRIORITY) {
            this.itemsForCurrentView = this.allItems
        } else {
            this.itemsForCurrentView = this.allItems.filter(item => {
                return item.Priority === filter;
            })
        }
        this.isLoaded = true;
    }

    handleClickExtend() {
        this.isExpanded = !this.isExpanded;
    }
}

Lines 3-29: That’s where we define all consts – columns – filter options and static data set. NOTE! To be able to take advantage of editable columns, you need to use real Salesforce data instead of fake data set.
Lines 44-48: Getter of dropdownTriggerClass which is used manage behaviour of dropdown button with filter options.
Lines 52-63: Handler for changing filter, we don’t change data for table if filter is the same as before. Also method to filter data is wrapped with setTimeout which allows us to controll spinner visibility. You can read more about this here.
Lines 66-75: Here we have handleFilterData method we actually does all the magic. Using javascript Array.prototype.filter() it returns to datatable only those records, which meet filter criteria.
Lines 77-78. This function is used to controll dropdown button behaviour.

Of course we also need to add some css to our component.

testListView.css

.lgc-bg {
    background-color: rgb(242, 242, 242);
}

.lgc-bg-inverse {
    background-color: rgb(22, 50, 92);
}

.label-hidden > label {
    display: none !important;
}

.icon_custom_class{
    margin-top: 5px;
}

.current_filter {
    color: rgb(8, 7, 7);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 1.125rem;
    font-weight: 700;
    line-height: 1.25;
    display: block;
    cursor: pointer;
}

.current_filter:hover {
    text-decoration: underline;
}

.custom_list_view {
    max-width: max-content;
}

.button_class {
    margin-left: 10px;
    margin-right: 10px;
  }

.list_view_container {
    max-width: max-content;
}

.list_view_and_table_container {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

In the last place we have to modify meta.xml file, to expose component to lightning pages.

testListView.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>

And that’s all, now we can finally put this component on record page and start to using it. So Let’s check out how does it work.

Conclusion

That’s how you can build custom List View in LWC and as you can see component works perfectly. Of course you can adjust it, for instance: keep actual filters in custom metadata. but this a good point to start.

If you liked it, then maybe you want to take a look on our other solutions: for example: Custom Validation or how to automatically refresh your record page.

5 5 votes
Article Rating
Subscribe
Notify of
guest
1 Comment
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
trackback

[…] say that you use data table with custom view like in this example. In such scenario you don’t call apex method to provide data to table, but just […]

Close Menu
1
0
Would love your thoughts, please comment.x
()
x