Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags more
Archives
Today
Total
관리 메뉴

파스타집

Multi-Select Quick Search Component 본문

Salesforce/LWC

Multi-Select Quick Search Component

Vongole 2022. 11. 26. 00:07

Combobox + Quick Search + Multi-Select 세 가지 기능을 동시에 수행한다.

customSearch.html
<template>
    <lightning-card title="Custom Search" icon-name="utility:search">
        <div class="slds-p-horizontal_small">
            <div class="search-container">
                <lightning-input type="search" label="Search" onchange={searchItems} onfocus={showItems} onfocusout={hideItems} placeholder="Search..."></lightning-input>
                <div class="slds-tabs_card slds-float_left slds-hide">
                    <ul>
                        <template for:each={dataList} for:item="data">
                            <li class="slds-truncate" key={data.value} onmousedown={selectItem} data-value={data.value}>{data.label}</li>
                        </template>
                    </ul>
                </div>
                <template for:each={selectedDataList} for:item="selectedData">
                    <lightning-pill key={selectedData.value} label={selectedData.label} data-value={selectedData.value} onremove={removeItem}></lightning-pill>
                </template>
            </div>
        </div>
    </lightning-card>
</template>
customSearch.js
import {LightningElement, track} from 'lwc';

const SLDS_HIDE = 'slds-hide';
const SELECTED = 'selected';

export default class CustomSearch extends LightningElement {
    @track dataList = [
        {label: 'GenePoint', value: '0015h000015pZvtAAE'},
        {label: 'United Oil & Gas, UK', value: '0015h000015pZvrAAE'},
        {label: 'United Oil & Gas, Singapore', value: '0015h000015pZvsAAE'},
        {label: 'Edge Communications', value: '0015h000015pZvjAAE'},
        {label: 'Burlington Textiles Corp of America', value: '0015h000015pZvkAAE'},
        {label: 'Pyramid Construction Inc.', value: '0015h000015pZvlAAE'},
        {label: 'Dickenson plc', value: '0015h000015pZvmAAE'},
        {label: 'Grand Hotels & Resorts Ltd', value: '0015h000015pZvnAAE'},
        {label: 'Express Logistics and Transport', value: '0015h000015pZvpAAE'},
        {label: 'University of Arizona', value: '0015h000015pZvqAAE'},
        {label: 'United Oil & Gas Corp.', value: '0015h000015pZvoAAE'},
        {label: 'sForce', value: '0015h000015pZvuAAE'},
        {label: '권리에 대한 샘플 계정', value: '0015h0000142yc3AAA'}
    ];
    @track selectedDataList = [];

    get tabCard() {
        return this.template.querySelector('.slds-tabs_card');
    }
    get allItems() {
        return [...this.template.querySelectorAll('li')];
    }
    get visibleItems() {
        return [...this.template.querySelectorAll(`li:not(.${SELECTED})`)];
    }

    searchItems({target: {value}}) {
        if(value) {
            this.visibleItems.forEach(item => item.innerText.toUpperCase().includes(value.toUpperCase()) ? item.classList.remove(SLDS_HIDE) : item.classList.add(SLDS_HIDE));
        } else {
            this.visibleItems.forEach(item => item.classList.remove(SLDS_HIDE));
        }
    }

    showItems() {
        this.tabCard.classList.remove(SLDS_HIDE);
    }

    hideItems() {
        this.tabCard.classList.add(SLDS_HIDE);
    }

    selectItem({target: {dataset: {value}}}) {
        this.visibleItems.find(item => item.dataset.value === value).classList.add(SELECTED);
        this.selectedDataList.push(this.dataList.find(data => data.value === value));
        this.hideItems();
    }

    removeItem({target: {dataset: {value}}}) {
        this.selectedDataList = this.selectedDataList.filter(selectedData => selectedData.value !== value);
        this.allItems.find(item => item.dataset.value === value).classList.remove(SELECTED);
    }
}
customSearch.css
li {
    padding: 6px
}

li:hover {
    background-color: var(--lwc-colorBackgroundRowHover)
}

.slds-tabs_card {
    position: absolute;
    z-index: 1;
    padding: 3px 0;
    width: 100%;
    max-height: 200px;
    overflow: auto;
    cursor: pointer
}

.selected {
    display: none;
}

.search-container {
    max-width: 500px;
    position: relative
}

요구사항으로 정말 많이 나오는데 구현하기 귀찮은 컴포넌트 중 하나.

dataList를 받아온 후 selectedDataList를 반환하는 패턴이므로 입맛에 맞게 수정해서 사용하면 된다.

항목에 아이콘을 추가하거나 비동기 검색기능을 넣어도 괜찮을 것 같다.

 

컴포넌트 작동 원리
  1. Input에 focus가 발생하면 dropdown을 보여주고, focus가 빠지면 dropdown을 숨긴다.
  2. Quick Search를 하는 경우 검색 결과에 해당하지 않는 항목에 .slds-hide를 붙여서 일시적으로 숨긴다.
  3. 선택된 항목은 .selected를 붙여서 영구적으로 숨기고 selectedDataList에 추가한다. 이를 컴포넌트에 <lightning-pill>태그로 나열한다.
  4. 선택된 항목을 제거하는 경우, 해당 항목을 selectedDataList에서 제거하고 dataList에서 해당 항목의 .selected를 제거하여 다시 보여준다. 
주의사항
  • Dropdown에 click이벤트를 거는 경우 input의 focus가 우선순위로 빠져서 클릭하기 전에 dropdown이 먼저 숨는다. 따라서 dropdown에 mousedown이벤트를 걸어서 focus이벤트가 발생하기 전에 선택된 항목을 가로채야 한다.
  • Dropdown을 <template if>로 처리할 경우 input에 focus가 발생했을때 rerender가 된다. 이는 동적으로 생성된 .slds-hide, .selected 클래스를 날려버리기 때문에 CSS를 통해 visibility를 조정을 하는 방식으로 만들어야 한다.
  • Visibility조정이 아닌 dataList를 직접 건드려서 dropdown 항목을 제어할 경우, 선택된 항목을 제거해서 다시 돌려놓을때 정렬이 깨져버린다. 따라서 dataList를 직접 건드리면 안된다.
Comments