Thursday, 19 May 2022

Lightning Web Component - Making custom dual list box with same functionality as standard Lightning-dual-listbox

SCENARIO

While working on one of the requirements for a Banking sector project for a client based out of London, UK there was a requirement to make a custom-made dual list box instead of the standard lightning-dual-listbox.

CHALLENGE

The client wanted a custom dual listbox instead of a standard because they wanted to implement different functionality on each button which was not possible in the standard lightning-dual-listbox.

APPROACH

First, We created a custom dual list box having the same UI as standard lightning-dual-listbox having 2 list boxes and 4 buttons (right, left, up and down) using the code provided by the standard lightning design system.

We made sure that multiple values can be added from the available to the selected list box and vice-versa.

Refer to the below code that we used to create custom dual listbox.

customDualListBox.html

<template>
    <lightning-card title="Custom Dual List Box" icon-name="custom:custom63">
        <div class="slds-form-element slds-box" role="group" aria-labelledby="picklist-group-label">
            <span id="picklist-group-label" class="slds-form-element__label slds-form-element__legend">Select
                Options</span>
            <div class="slds-form-element__control">
                <div class="slds-dueling-list">
                    <div class="slds-assistive-text" id="drag-live-region" aria-live="assertive"></div>
                    <div class="slds-assistive-text" id="option-drag-label">Press space bar when on an item, to move it
                        within the list. Cmd/Ctrl plus left and right arrow keys, to move items between lists.</div>
                    <div class="slds-box">
                        <!-- Available Options -->
                        <div class="slds-dueling-list__column slds-border_right">
                            <span class="slds-form-element__label" id="label-107">Available Options</span>
                            <div class="slds-dueling-list__options slds-border_right">
                                <ul aria-describedby="option-drag-label" aria-labelledby="label-107"
                                    aria-multiselectable="true" class="slds-listbox slds-listbox_vertical"
                                    role="listbox">
                                    <template for:each="{availableOptions}" for:item="option">
                                        <li class="slds-listbox__item" data-value="{option.value}" key="{option.value}"
                                            onclick="{handleSelectionForAvailable}">
                                            <div data-val="{option.value}"
                                                class="slds-listbox__option slds-listbox__option_plain slds-media slds-media_small slds-media_inline">
                                                <span class="slds-media__body">
                                                    <span title="English" class="slds-truncate">{option.label}</span>
                                                </span>
                                            </div>
                                        </li>
                                    </template>
                                </ul>
                            </div>
                        </div>
                        <!-- Right left buttons -->
                        <div class="slds-dueling-list__column">
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection to Second Category" onclick="{handleRightClick}">
                                <lightning-icon icon-name="utility:right" size="x-small" alternative-text="right"
                                    title="right"></lightning-icon>
                                <span class="slds-assistive-text">Move Selection to Second Category</span>
                            </button>
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection to First Category" onclick="{handleLeftClick}">
                                <lightning-icon icon-name="utility:left" size="x-small" alternative-text="left"
                                    title="left"></lightning-icon>
                                <span class="slds-assistive-text">Move Selection to First Category</span>
                            </button>
                        </div>
                        <!-- Selected Options -->
                        <div class="slds-dueling-list__column">
                            <span class="slds-form-element__label" id="label-108">Selected Options</span>
                            <div class="slds-dueling-list__options">
                                <ul aria-describedby="option-drag-label" aria-labelledby="label-108"
                                    aria-multiselectable="true" class="slds-listbox slds-listbox_vertical"
                                    role="listbox">
                                    <template for:each="{selectedOptions}" for:item="option">
                                        <li class="slds-listbox__item" data-value="{option.value}" key="{option.value}"
                                            onclick="{handleSelectionForSelected}">
                                            <div data-val="{option.value}"
                                                class="slds-listbox__option slds-listbox__option_plain slds-media slds-media_small slds-media_inline">
                                                <span class="slds-media__body">
                                                    <span title="selected" class="slds-truncate">{option.label}</span>
                                                </span>
                                            </div>
                                        </li>
                                    </template>
                                </ul>
                            </div>
                        </div>
                        <!-- Up down buttons -->
                        <div class="slds-dueling-list__column">
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection up" onclick="{handleUpClick}">
                                <lightning-icon icon-name="utility:up" size="x-small" alternative-text="up" title="up">
                                </lightning-icon>
                                <span class="slds-assistive-text">Move Selection up </span>
                            </button>
                            <button class="slds-button slds-button_icon slds-button_icon-container"
                                title="Move Selection down" onclick="{handleDownClick}">
                                <lightning-icon icon-name="utility:down" size="x-small" alternative-text="down"
                                    title="down"></lightning-icon>
                                <span class="slds-assistive-text">Move Selection down </span>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </lightning-card>
</template>

customDualListBox.js

import { LightningElement, track } from "lwc";
export default class customDualListBox extends LightningElement {
    @track availableOptions = [
        { label: "Option 1", value: "o1" },
        { label: "Option 2", value: "o2" },
        { label: "Option 3", value: "o3" },
        { label: "Option 4", value: "o4" },
        { label: "Option 5", value: "o5" },
        { label: "Option 6", value: "o6" },
        { label: "Option 7", value: "o7" },
    ];

    @track selectedAvailableOptions = [];
    @track selectedOptions = [];
    @track selectedSelectedOptions = [];

    handleSelectionForAvailable(event) {
        if (this.selectedAvailableOptions.filter((option) => option === event.currentTarget.dataset.value).length) {
            this.selectedAvailableOptions = this.selectedAvailableOptions.filter((option) => option !== event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "false");
        } else {
            this.selectedAvailableOptions.push(event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "true");
        }
    }

    handleSelectionForSelected(event) {
        if (this.selectedSelectedOptions.filter((option) => option === event.currentTarget.dataset.value).length) {
            this.selectedSelectedOptions = this.selectedSelectedOptions.filter((option) => option !== event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "false");
        } else {
            this.selectedSelectedOptions.push(event.currentTarget.dataset.value);
            event.currentTarget.setAttribute("aria-selected", "true");
        }
    }

    handleRightClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedAvailableOptions));
        if (tempSelectedOptions.length) {
            for (let selectedOption of tempSelectedOptions) {
                this.selectedOptions.push(...this.availableOptions.filter((option) => option.value === selectedOption));
                this.availableOptions = this.availableOptions.filter((option) => option.value !== selectedOption);
            }
        }
        this.selectedAvailableOptions = [];
    }

    handleLeftClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedSelectedOptions));
        if (tempSelectedOptions.length) {
            for (let selectedOption of tempSelectedOptions) {
                this.availableOptions.push(...this.selectedOptions.filter((option) => option.value === selectedOption));
                this.selectedOptions = this.selectedOptions.filter((option) => option.value !== selectedOption);
            }
        }
        this.selectedSelectedOptions = [];
    }

    handleUpClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedSelectedOptions));
        if (tempSelectedOptions.length == 1) {
            for (let i = 0; i < this.selectedOptions.length; i++) {
                if (i != 0 && this.selectedOptions[i].value === tempSelectedOptions[0]) {
                    [this.selectedOptions[i], this.selectedOptions[i - 1]] = [this.selectedOptions[i - 1], this.selectedOptions[i]];
                    break;
                }
            }
        }
    }

    handleDownClick() {
        var tempSelectedOptions = JSON.parse(JSON.stringify(this.selectedSelectedOptions));
        if (tempSelectedOptions.length == 1) {
            for (let i = 0; i < this.selectedOptions.length; i++) {
                if (i != this.selectedOptions.length - 1 && this.selectedOptions[i].value === tempSelectedOptions[0]) {
                    [this.selectedOptions[i], this.selectedOptions[i + 1]] = [this.selectedOptions[i + 1], this.selectedOptions[i]];
                    break;
                }
            }
        }
    }
}

  • handleSelectionForAvailable: This function is used to select/unselect value(s) under the available options section.
  • handleSelectionForSelected: This function is used to select/unselect value(s) selected options section.
  • handleLeftClick: This function is responsible to add the selected value(s) in the selected options section and remove values from available options section.
  • handleRightClick: This function is responsible to add the selected value(s) in the available options section and remove values from selected options section.
  • handleDownClick: This function is used to move values one step down in the selected options section.
  • handleUpClick: This function is used to move values one step up in the selected options section.

customDualListBox.xml

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


OUTPUT


CONCLUSION

By using the above code, We are able to make a custom dual list box with the same functionality as the standard Lightning-dual-listbox.


If you have any questions you can reach out our Salesforce Consulting team here.

No comments:

Post a Comment