페이징 만들기

ahncheer·2023년 11월 6일
0

LWC & LWR

목록 보기
40/52

다른 라이브러리를 사용하지 않고 JS와 CSS로만 만들었습니다.

1. lwc037Paging

1-1) lwc037Paging.html

<template>
    <div class="wrapper">
        <div class="input-wrap">
            <lightning-input type="text" label="총 페이징 개수" class="total-input" value={numData.total}></lightning-input>
            <lightning-input type="text" label="한번에 보여지는 페이징 개수" class="pagenum-input" value={numData.showPage}></lightning-input>
            <lightning-button label="설정" title="Non-primary action" onclick={handleClick} 
            class="slds-m-left_x-small"></lightning-button>
        </div>
        <c-lwc038-paging-child
            total={numData.total}
            show-count={numData.showPage}
            onnumchange={numChange}
        ></c-lwc038-paging-child>
        <p class="current-txt">[lwc037Paging] 현재 페이지 번호 : {currentPageNum}</p>
    </div>
</template>

1-2) lwc037Paging.js

import { LightningElement, track} from 'lwc';

export default class Lwc037Paging extends LightningElement {
    // table 페이징에 사용
    @track numData = {
        total : 25, showPage : 10
    };
    @track showInput = true; //reset에 사용
    @track currentPageNum;

    async handleClick(){
        let totalEl = this.template.querySelector('.total-input');
        if(totalEl){
            console.log('totalEl.value : ', totalEl.value);
            this.numData.total = totalEl.value;
        }
        let pagenumEl = this.template.querySelector('.pagenum-input');
        if(pagenumEl){
            console.log('pagenumEl.value : ', pagenumEl.value);
            this.numData.showPage = pagenumEl.value;
        }
    }
    numChange(e){
        console.log('e : ', JSON.parse(JSON.stringify(e.detail)));
        this.currentPageNum = e.detail.activeNum;

    }
}

1-3) lwc037Paging.css

.wrapper{
    background-color: #efefef;
    padding: 30px 20px;
}
.input-wrap{
    width: 500px;
    display: grid;
    gap: 20px;
    grid-template-columns: 3fr 3fr 2fr;
    padding-bottom: 30px;
    align-items: end;
}
.input-area {
    display: flex;
    align-items: flex-end;
}
.current-txt{
    margin-top: 30px;
    background-color: #ddd;
    padding: 5px 17px 5px 5px;
    border-radius: 5px;
    display: inline-flex;
}

2. lwc038PagingChild

2-1) lwc038PagingChild.html

<template>
    <div class="wrapper" data-total={total} data-count={showCount}>
        <div class="paging-wrap">
            <span class="first" onclick={goFirst}></span>
            <span class="prev" onclick={goPrev}></span>
            <div class="show-menu">
                <div class="num-wrap">
                <template lwc:if={pagingList} for:each={pagingList} for:item="item" for:index="idx">
                    <a class="paging-num" key={item} onclick={numActive} data-idx={idx}>
                        {item.label}
                    </a>
                </template>
            </div>
            </div>
            <span class="next" onclick={goNext}></span>
            <span class="last" onclick={goLast}></span>
        </div>
    </div>
</template>

2-2) lwc038PagingChild.js

import { LightningElement, track, api} from 'lwc';

export default class Lwc038PagingChild extends LightningElement {
    @api total;/* total 세팅 */
    @api showCount;/* 한번에 보여지는 숫자의 개수  세팅 */
    @track defaultTotal = 0;
    @track defaultShowCount = 0;

    @track pagingList = [];
    @track activeNum = 0; // 현재 활성화 되어있는 페이지 (0부터시작)
    @track activePage = 0;

    @track isFirstRender = true;
    async renderedCallback() {
        // console.log('-----------------------------------------------');
        // console.log('this.isFirstRender == true : ', this.isFirstRender == true);
        // console.log('this.defaultTotal != this.total : ', this.defaultTotal != this.total);
        // console.log('(this.defaultShowCount != this.showCount) : ', (this.defaultShowCount != this.showCount));

        if((this.defaultTotal != this.total) || (this.defaultShowCount != this.showCount) || (this.isFirstRender == true)){
            
            let newList = [];
            for (let index = 0; index < this.total; index++) {
                newList.push({label : (index + 1)});
            }
            this.pagingList = newList;
            let numElAll = this.template.querySelectorAll('.num-wrap a');
            if(numElAll.length == this.total){
                let oneWidth = this.total > 999 ? 50 : 35;
                numElAll.forEach((el) => { el.style.minWidth = oneWidth + 'px' });
                console.log('numElAll : ', numElAll.length);

                this.isFirstRender = false;
                this.activeNum = 0;
                this.activePage = 0;
                this.defaultTotal = this.total;
                this.defaultShowCount = this.showCount;
                // console.log('this.pagingList : ', this.pagingList);
                await this.setPagingPosition();
                
            }
        }
    }

    // 해당 탭의 Number를 클릭했을 때
    numActive(e){
        let idx = e.currentTarget.dataset.idx;
        console.log('idx : ', idx);
        this.activeNum = idx;
        this.setPagingPosition();
    }

    // 다음 페이지 이동
    goNext(){
        let pageNum = this.activePage + 1;
        let nextNum = pageNum * this.showCount;
        if(nextNum < this.total){
            this.activeNum = nextNum < this.total ? nextNum : this.activeNum;
            this.activePage = pageNum;
            this.setPagingPosition();
        }
    }
    // 이전 페이지 이동
    goPrev(){
        if(this.activeNum != 0){
            let pageNum = this.activePage - 1 > 0 ? this.activePage - 1 : 0;
            let prevNum = pageNum * this.showCount;
            this.activeNum = prevNum >= 0 ? prevNum : this.activeNum;
            this.activePage = pageNum;
            this.setPagingPosition();
        }
    }
    
    //맨 처음 페이지로 이동 
    goFirst(){
        if(this.activePage != 0){
            this.activeNum = 0;
            this.activePage = 0;
            this.setPagingPosition();
        }
    }
    //맨 마지막 페이지로 이동 
    goLast(){
        if(((this.activePage + 1) * this.showCount) < this.total){
            let pageNum = this.total % this.showCount == 0 ? (this.total/this.showCount - 1) : Math.floor(this.total/this.showCount);
            this.activePage = pageNum;
            this.activeNum = pageNum * this.showCount;
            this.setPagingPosition();
        }
    }

    setPagingPosition(){
        // 보여지는 값의 max-width 변경
        let showMenu = this.template.querySelector('.show-menu');
        let oneWidth = this.total > 999 ? 50 : 35;
        // console.log('oneWidth : ', oneWidth);
        if((this.activePage + 1) * this.showCount < this.total ){
            showMenu.style.maxWidth = (this.showCount * oneWidth) + 'px';
        }else{
            let lastChild = this.total - (this.activePage * this.showCount);
            showMenu.style.maxWidth = (lastChild * oneWidth) + 'px';
        }

        // 보여지는 값의 위치 변경
        let numWrap = this.template.querySelector('.num-wrap');
        // console.log('this.showCount : ', this.showCount, ', this.activePage : ', this.activePage);
        numWrap.style.left = (this.activePage * this.showCount * -1 * oneWidth) + 'px';
        let numElAll = this.template.querySelectorAll('.num-wrap a');
        numElAll.forEach((el) => { el.style.minWidth = oneWidth + 'px' });
        // console.log('numElAll : ', numElAll.length)

        let numList = this.template.querySelectorAll('.paging-num');
        // 버튼 활성화 표시 
        numList.forEach((el, index) => { 
            if(this.activeNum == index){
                // console.log('this.activeNum : , ', this.activeNum);
                el.classList.add("active");
            }else{
                el.classList.remove("active");
            }
        }); 

        // 마지막 페이지인지 확인하고 disabled하기
        let isLastPage = ((this.activePage + 1) * this.showCount) >= this.total;
        let nextCmp = this.template.querySelector('.next');
        let lastCmp = this.template.querySelector('.last');
        if(isLastPage){
            this.addDisabled(nextCmp);
            this.addDisabled(lastCmp);
        }else{
            this.removeDisabled(nextCmp);
            this.removeDisabled(lastCmp);
        }

        // 첫번째 페이지인지 확인하고 disabled하기
        let isFirstPage = (this.activePage == 0);
        let firstCmp = this.template.querySelector('.first');
        let prevCmp = this.template.querySelector('.prev');
        if(isFirstPage){
            this.addDisabled(firstCmp);
            this.addDisabled(prevCmp);
        }else{
            this.removeDisabled(firstCmp);
            this.removeDisabled(prevCmp);
        }

        this.sendCurrentPage();
    }
    addDisabled(item){
        item.setAttribute('disabled', 'disabled');
        item.classList.add("disabled");
    }
    removeDisabled(item){
        item.removeAttribute('disabled');
        item.classList.remove("disabled");
    }

    // 현재 활성화된 페이지가 몇페이지인지 보내줌 
    sendCurrentPage(){
        // console.log('▶ 현재 활성화된 페이지 this.activeNum + 1 : ', (Number(this.activeNum) + 1));
        const customEvt = new CustomEvent('numchange', {
            detail: {
                activeNum: Number(this.activeNum) + 1
            }
        });
        this.dispatchEvent(customEvt);
    }

}

2-3) lwc038PagingChild.css

.paging-wrap{
    display: flex;
    gap: 5px;
    align-items: center;
}
.show-menu{
    width: 100%;
    overflow: hidden;
    overflow-x: hidden;
    margin: 0 10px;
}
.num-wrap{
    /* left: -200px; */
    transition: all 0.2s ease-out;
    position: relative;
    display: flex;
}
.num-wrap .paging-num{
    text-align:center;
    color: #282828;
    text-decoration: none;
    position: relative;
}
.num-wrap .paging-num.active{
    font-weight: bold;
    text-decoration: dashed;
}
.num-wrap .paging-num.active:after{
    content: '';
    position: absolute;
    bottom: 0px;
    width: 40%;
    height: 1px;
    background-color: #282828;
    left: 50%;
    transform: translateX(-50%);
}
.common-pagination a{
    display: inline-flex;
    text-align: center;
}
.next.disabled, .last.disabled, .first.disabled, .prev.disabled{
    opacity: 0.3;
}
.next, .last, .first, .prev{
    width: 16px;
    height: 16px;
    display: inline-flex;
    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
}
.next{
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cg id='그룹_1' data-name='그룹 1' transform='translate(-49 -128)'%3E%3Cg id='사각형_1' data-name='사각형 1' transform='translate(49 128)' fill='%23fff' stroke='%23323145' stroke-linejoin='round' stroke-width='1'%3E%3Crect width='15' height='15' stroke='none'/%3E%3Crect x='0.5' y='0.5' width='14' height='14' fill='none'/%3E%3C/g%3E%3Cpath id='패스_2' data-name='패스 2' d='M0-.5l5,5-5,5' transform='translate(54 131)' fill='none' stroke='%23323145' stroke-linejoin='round' stroke-width='1'/%3E%3C/g%3E%3C/svg%3E%0A");
}
.last{
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cg id='그룹_2' data-name='그룹 2' transform='translate(-49 -128)'%3E%3Cpath id='패스_1' data-name='패스 1' d='M0,0' transform='translate(57.5 133.5)' fill='none' stroke='%23707070' stroke-width='1'/%3E%3Cg id='그룹_1' data-name='그룹 1'%3E%3Cg id='사각형_1' data-name='사각형 1' transform='translate(49 128)' fill='%23fff' stroke='%23323145' stroke-linejoin='round' stroke-width='1'%3E%3Crect width='15' height='15' stroke='none'/%3E%3Crect x='0.5' y='0.5' width='14' height='14' fill='none'/%3E%3C/g%3E%3Cpath id='패스_2' data-name='패스 2' d='M0-.5l5,5-5,5' transform='translate(56 131)' fill='none' stroke='%23323145' stroke-linejoin='round' stroke-width='1'/%3E%3Cpath id='패스_3' data-name='패스 3' d='M0-.5l5,5-5,5' transform='translate(53 131)' fill='none' stroke='%23323145' stroke-linejoin='round' stroke-width='1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A");
}
.first{
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cg id='그룹_2' data-name='그룹 2' transform='translate(64 143) rotate(180)'%3E%3Cpath id='패스_1' data-name='패스 1' d='M0,0' transform='translate(57.5 133.5)' fill='none' stroke='%23707070' stroke-width='1'/%3E%3Cg id='그룹_1' data-name='그룹 1'%3E%3Cg id='사각형_1' data-name='사각형 1' transform='translate(49 128)' fill='%23fff' stroke='%23323145' stroke-linejoin='round' stroke-width='1'%3E%3Crect width='15' height='15' stroke='none'/%3E%3Crect x='0.5' y='0.5' width='14' height='14' fill='none'/%3E%3C/g%3E%3Cpath id='패스_2' data-name='패스 2' d='M0-.5l5,5-5,5' transform='translate(56 131)' fill='none' stroke='%23323145' stroke-linejoin='round' stroke-width='1'/%3E%3Cpath id='패스_3' data-name='패스 3' d='M0-.5l5,5-5,5' transform='translate(53 131)' fill='none' stroke='%23323145' stroke-linejoin='round' stroke-width='1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A");
}
.prev{
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cg id='사각형_1' data-name='사각형 1' transform='translate(15 15) rotate(180)' fill='%23fff' stroke='%23323145' stroke-linejoin='round' stroke-width='1'%3E%3Crect width='15' height='15' stroke='none'/%3E%3Crect x='0.5' y='0.5' width='14' height='14' fill='none'/%3E%3C/g%3E%3Cpath id='패스_2' data-name='패스 2' d='M0-.5l5,5-5,5' transform='translate(10 12) rotate(180)' fill='none' stroke='%23323145' stroke-linejoin='round' stroke-width='1'/%3E%3C/svg%3E%0A");
}

3. 결과 확인 (gif)

profile
개인 공부 기록용.

0개의 댓글