LWC에서 검색 기능 추가한 Custom Combobox + Checkbox 만들기

ahncheer·2022년 10월 18일
0

LWC & LWR

목록 보기
19/52

● 실행 화면 미리보기

● 코드 작성하기

※ lwc023CreateCustomComboboxGroup.html

<template>
    <div class="test">
        <div tabindex='0' onfocus={openBg} onblur={closeBg} class="custom-dropdown">
            <!-- visual input -->
            <div class={dropdownObj.isOpenCss}> <!-- Class명 - visual-input + no-data -->
                <p class="info-msg">수강할 과목을 선택해 주세요</p>
                <p class="show-data">{testValueString}</p>
            </div>
            <!-- dropdown checkbox -->
            <template if:true={dropdownObj.isMainOpen}>
                <div class="content">
                    <input type="text" value={searchKeyword} tabindex='0' 
                    onclick={openInput} onblur={closeInput}
                    onkeyup={handleSearchKeyword} />
                    <div class="icon-wrap">
                        <template if:true={searchKeyword}>
                            <span onclick={deleteSearchKeyword} class="icon delete-icon"></span>
                        </template>
                    </div>
                    <template for:each={testCheckBoxList} for:item="item" for:index="idx">
                        <div key={item.value} data-index={idx} 
                        class={item.activeCss} onclick={testValueChange}>
                            <span class="custom-checkbox"></span>  
                            <p><lightning-formatted-rich-text value={item.splitLabel}>
                            </lightning-formatted-rich-text></p>
                            <!-- <p>{item.newLabel}</p> -->
                        </div>
                    </template>
                </div>
            </template>
        </div>
    </div>
</template>

※ lwc023CreateCustomComboboxGroup.js

import { LightningElement, track } from 'lwc';
import {subjectList} from './tempData.js';


export default class Lwc023CreateCustomComboboxGroup extends LightningElement {
    @track dropdownObj = {
        isMainOpen : false, 
        isBgOpen : false,
        isInputOpen : false,
        isOpenCss : 'visual-input no-data'
        };
        @track defaultCheckBoxList = subjectList;
        @track testCheckBoxList = [];
        @track testValueList = []; //선택된 value값
        @track testValueString = ''; //선택된 라벨값
        @track searchKeyword = '';

    connectedCallback() {
        this.defaultCheckBoxList.forEach((el, index) => { 
            el.activeCss = 'box-wrap';
            el.splitLabel = el.label; //splitLabel 
        });
        this.testCheckBoxList = this.defaultCheckBoxList;
    }
    //전체 화면 열고 닫기 
    openBg(){
        console.log('▶  openBg ');
        this.dropdownObj.isBgOpen = true;
        this.dropdownMain();
    }
    closeBg(e){
        console.log('▶  closeBg ', e.relatedTarget);
        if(!e.relatedTarget){
            this.dropdownObj.isBgOpen = false;
            this.dropdownMain();
        }
    }
    openInput(){
        console.log('▷  openInput ');
        this.dropdownObj.isInputOpen = true;
        this.dropdownMain();
    }
    closeInput(){
        console.log('▷  closeInput ');
        this.dropdownObj.isInputOpen = false;
        this.dropdownObj.isBgOpen = false;
        this.dropdownMain();
    }
    dropdownMain(){
        console.log('◈  dropdownMain ');
        console.log(this.dropdownObj.isBgOpen, '//', this.dropdownObj.isInputOpen);
        this.dropdownObj.isMainOpen = (this.dropdownObj.isBgOpen || this.dropdownObj.isInputOpen);
    }

    //데이터 있는지 확인 후 input영역에 보여줄 영역 선택하기
    checkHaveData(){
        console.log('testValueChange > testValueList : ', JSON.parse(JSON.stringify(this.testValueList)));
        console.log('▶▶  checkHaveData > length : ', this.testValueList.length);
        let haveData = this.testValueList.length > 0;
        this.dropdownObj.isOpenCss = haveData ? 'visual-input' : 'visual-input no-data';
    }
    //데이터 바뀔 때 (콤보박스 선택되었을 때)
    testValueChange(e){
        console.log('▶▶  testValueChange ');
        e.preventDefault();
        let targetIndex = e.currentTarget.dataset.index;
        let targetValue = this.testCheckBoxList[targetIndex].value;
        console.log('targetIndex : ', targetIndex);

        //값이 있으면 추가, 아니면 삭제 
        let hasValue = this.testValueList.findIndex((el) => el.value == targetValue);
        console.log('hasValue : ', hasValue);
        if(hasValue > -1){
            this.testValueList.splice(hasValue, 1);
            this.testCheckBoxList[targetIndex].activeCss = 'box-wrap';
        }else{
            this.testValueList.push(this.testCheckBoxList[targetIndex]);
            this.testCheckBoxList[targetIndex].activeCss = 'is-checked box-wrap';
        }
        console.log('testValueChange > testCheckBoxList : ', JSON.parse(JSON.stringify(this.testCheckBoxList)));
        this.testValueString = '';
        this.testValueList.forEach((el, index) => { 
            if(el.activeCss == 'is-checked box-wrap'){
                this.testValueString += el.label;
                if((index + 1) < this.testValueList.length){
                    this.testValueString += ', ';
                }
            }
            
        });
        this.checkHaveData();
    }
    //검색 키워드 입력받음 
    handleSearchKeyword(e){
        this.openInput();
        let keyword = e.target.value;
        this.searchKeyword = keyword;
        console.log('handleSearchKeyword > keyword : ', keyword);
        this.editList(keyword);
    }

    //리스트 목록 수정하기 
    editList(keyword){
        let newList = [];
        if(keyword == ''){
            this.testCheckBoxList = this.defaultCheckBoxList;
        }else{
            this.defaultCheckBoxList.forEach((el, index) => {
                let hasKeyword = el.label.indexOf(keyword);
                //console.log('hasKeyword : ',hasKeyword);
                if(hasKeyword !== -1){
                    newList.push(el);
                }
            });
            console.log('editList > newList : ', JSON.parse(JSON.stringify(newList)));
            this.testCheckBoxList = newList;
        }
        this.changeKeywordColor(keyword);
    }

    //리스트에서 해당되는 Keyword값만 글씨색 변경
    changeKeywordColor(keyword){
        this.testCheckBoxList.forEach((el, index) => {
            if(keyword !== ''){
                let splitText = el.label.split(keyword);
                let newText = '';
                splitText.forEach((el, index) => {
                    newText = newText + el;
                    if(splitText.length > (index + 1)){
                        newText = newText + '<span style="font-weight: bold;color: #DB2F1C;"">' + keyword + '</span>';
                    }
                });
                console.log('newLabel : ', JSON.parse(JSON.stringify(newText)))
                el.splitLabel = newText;
            }else{
                el.splitLabel = el.label;
            }
        });
    }
    //검색 키워드 삭제하기 
    deleteSearchKeyword(e){
        this.searchKeyword = '';
        this.editList(this.searchKeyword);
    }
}

※ lwc023CreateCustomComboboxGroup.css

.test {
    background-color: #efefef;
    max-width: 1600px;
    margin: 20px auto;
    padding: 20px;
}
.custom-dropdown{
    display: block;
    background-color: #d3d0e2;
}

.visual-input .info-msg,
.visual-input.no-data .show-data{
    display: none;
}
.visual-input.no-data .info-msg,
.visual-input .show-data{
    display: block;
}
.visual-input{
    padding: 13px 20px;
    border-radius: 5px;
    border: 1px solid #333;
    color: #333333;
    background-color: #FFF;
}
.visual-input P{
    margin: 0;
}
.content{
    margin-top: 2px;
    padding: 20px;
    border-radius: 5px;
    border: 1px solid #333333;
    background-color: #FFFFFF;
    position: relative;
}
.content input{
    width: calc(100% - 30px);
    padding: 13px 15px;
    border: 1px solid #D2D2D2;
    border-radius: 5px;
}
.box-wrap {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 20px;
    margin-top: 18px;
}
.icon-wrap{
    display: flex;;
    position: absolute;
    top: 27px;
    right: 30px;
    gap: 5px;
}
.icon-wrap .icon{
    height: 30px;
    width: 30px;
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
}
.delete-icon{
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3E%3Cg id='패스_44266' data-name='패스 44266' fill='%23d1d8e9'%3E%3Cpath d='M 29.5 29.5 L 0.5 29.5 L 0.5 0.5 L 29.5 0.5 L 29.5 29.5 Z' stroke='none'/%3E%3Cpath d='M 1 1 L 1 29 L 29 29 L 29 1 L 1 1 M 0 0 L 30 0 L 30 30 L 0 30 L 0 0 Z' stroke='none' fill='%23bfcae1'/%3E%3C/g%3E%3Cline id='선_374' data-name='선 374' x2='16' y2='16' transform='translate(7 7)' fill='none' stroke='%2367789e' stroke-width='2'/%3E%3Cpath id='패스_44265' data-name='패스 44265' d='M16,2.231l-16,16' transform='translate(7 4.769)' fill='none' stroke='%2367789e' stroke-width='2'/%3E%3C/svg%3E%0A");
}
.custom-checkbox{
    width: 20px;
    height: 20px;
    display: block;
    background-size: cover;
    background-repeat: no-repeat;
    margin-right: 5px;
    background-image: url("data:image/svg+xml,%3Csvg id='checkbox' xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18'%3E%3Cg id='사각형_27' data-name='사각형 27' fill='%23fff' stroke='%23bbb' stroke-width='1'%3E%3Crect width='18' height='18' stroke='none'/%3E%3Crect x='0.5' y='0.5' width='17' height='17' fill='none'/%3E%3C/g%3E%3C/svg%3E%0A");
}
.is-checked .custom-checkbox{
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18'%3E%3Crect id='사각형_27' data-name='사각형 27' width='18' height='18' fill='%23281cdb'/%3E%3Cpath id='check-bold' d='M6.072,12.88,1.79,8.683l1.5-1.457L6.072,9.971,11.294,4.88l1.5,1.457Z' transform='translate(2.21 0.12)' fill='%23fff'/%3E%3C/svg%3E%0A");
}

※ tempData.js (데이터 파일)

const subjectList = [
    {label : '수학', value : 'math'},
    {label : '국어', value : 'Korean'},
    {label : '영어', value : 'english'},
    {label : '과학', value : 'science'},
    {label : '한국사', value : 'Koreanhistory'},
]

export { subjectList };

● 결과 확인하기

여러가지 방법이 있으니 본인에게 맞는 방법을 사용하면 좋을 것 같습니다.

profile
개인 공부 기록용.

0개의 댓글