Vanilla JS PungList Project

DW J·2022년 9월 19일
0

project_punglist

목록 보기
1/1

PungList 프로젝트 요구사항

기능 구현을 목적으로 만든 프로젝트 입니다
앞으로 스터디를 통해 배워나갈 (prototype, class)등을 사용하여 리팩토링을 진행 할 예정입니다

  • 내용을 입력하고 시간을 선택하면 항목이 생성됩니다
  • 생성된 항목은 선택한 시간이 지나면 펑~ 삭제됩니다
  • 각 항목의 남아있는 시간이 실시간으로 나타납니다
  • 항목들은 남아있는 시간 내림차순으로 정렬되어 나옵니다
  • 각 항목에는 삭제버튼이 있고 클릭시 바로 삭제됩니다
  • 각 항목에는 시간을 5초 연장하는 버튼이 있습니다
  • 각 항목에는 시간을 중지하는 버튼이 있습니다. 중지하면 시작버튼이 나오고 시작버튼을 클릭하면 해당 항목의 시간이 다시 흐릅니다
  • 전체 펑 리스트를 요약하여 출력합니다. 항목의 총 갯수와 평균 남은시간을 출력합니다
  • 초기화 버튼을 제공합니다. 모든 항목이 삭제됩니다
  • 모든 항목을 그대로 복사하여 2배로 만드는 버튼을 제공합니다
  • 전체 항목을 중지하는 버튼을 제공합니다. 이미 중지된 항목은 영향이 없습니다
  • 전체 항목을 시작하는 버튼을 제공합니다. 작동하고 있는 항목은 영향이 없습니다

1. HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PUNG</title>
    <style>
        .pungList {
            padding-left: 0;
        }

        .pungList > li {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }
        
        .pungList > li > div {
            margin: 0 10px;
        }
    </style>
</head>
<body>
    <h1>pung list</h1>
    <div class="pungAdd">
        <input type="text" class="pungContent" />
        <button type="button" data-second="5">5초</button>
        <button type="button" data-second="10">10초</button>
        <button type="button" data-second="20">20초</button>
    </div>
    <div>
        <p>총: <span id="pungCount">0</span> 건 평균 남은시간: <span id="pungAverge">0.0</span></p>
        <button class="pungReset">초기화</button>
        <button class="pungDouble">x2</button>
        <button class="pungSecondPlusAll" data-second="5">전체+5</button>
        <button class="pungStopAll">전체 중지</button>
        <button class="pungStartAll">전체 시작</button>
    </div>
    <ul class="pungList"></ul>
    <script src="./js/pung-list.js"></script>
</body>
</html>
  • 디자인 관련 요소는 적용하지 않는다
  • HTML은 최대한 심플하게 작성한다

2. JS

초기화 관련 - init

const pungList = {
    pungLists: [],

    timerLists: {},

    pungListsElement: null,

    // INITIALIZE ==============================================================================
    init: function () {
        this.initElement();
    },

    initElement: function () {
        const pungContent = document.querySelector(".pungContent");
        const pungAdds = document.querySelectorAll(".pungAdd > button");
        const pungReset = document.querySelector(".pungReset");
        const pungDouble = document.querySelector(".pungDouble");
        const pungSecondPlusAll = document.querySelector(".pungSecondPlusAll");
        const pungStopAll = document.querySelector(".pungStopAll");
        const pungStartAll = document.querySelector(".pungStartAll");

        this.pungListsElement = document.querySelector(".pungList");

        pungAdds.forEach(pungAdd => {
            pungAdd.addEventListener("click", this.handleClickTimer.bind(this, pungContent));
        });
        pungReset.addEventListener("click", this.handleClickRemoveAll.bind(this));
        pungDouble.addEventListener("click", this.handleClickListDoubleCopy.bind(this));        
        pungSecondPlusAll.addEventListener("click", this.handleClickSecondPlusAll.bind(this));
        pungStopAll.addEventListener("click", this.handleClickStopAll.bind(this));
        pungStartAll.addEventListener("click", this.handleClickStartAll.bind(this));
    },
}
  • 초기 element의 이벤트 셋팅을 위한 영역
  • element 이벤트를 바인딩하고 객체 내부에서 사용할 this값을 초기화

데이터 관련 - Data

// DATA ==============================================================================
.
..
...
    add: function (options) {
        const { content, timer, isToggle, timerID } = options;
        const newList = { id: `pung-list-${crypto.randomUUID()}`, isToggle, content, timer, timerID };

        this.pungLists = this.pungLists.concat(newList);
        this.pungLists.sort((a, b) => {
            return a.timer - b.timer;
        });
        this.addView(newList);
    },

    remove: function (id) {
        const removeList = this.getTarget(id);
        const removeListIdx = this.getTargetIdx(id);

        this.pungLists.splice(removeListIdx, 1);
        this.setTimerClear(removeList.timerID);
        this.setPungListAverge(removeList.timer);
        this.setPungListCounter();
        this.removeView(id);
    },

    update: function (id) {
        const updateList = this.getTarget(id);

        updateList.timer += 5;
        this.setTimerClear(updateList.timerID);
        this.updateView(id, updateList.timer);

        if (!updateList.isToggle) {
            this.setTimer(id, updateList.timer);
        }
    },

    toggle: function (id) {
        const toggleList = this.getTarget(id);
        const { timer, timerID } = toggleList;
        let { isToggle } = toggleList;
        
        toggleList.isToggle = !toggleList.isToggle;

        if (toggleList.isToggle) {
            this.setTimerClear(toggleList.timerID);
        }
        else {
            this.setTimer(id, toggleList.timer);
        }

        this.toggleView(id, toggleList.isToggle);
    },

    stop: function (id) {
        const stopList = this.getTarget(id);

        stopList.isToggle = true;
        this.setTimerClear(stopList.timerID);
        this.stopView(id);
    },

    start: function (id) {
        const startList = this.getTarget(id);

        startList.isToggle = false;
        this.setTimer(id, startList.timer);
        this.startView(id);
    },
...
..
.
  • pung list의 데이터에 관련 된 로직이 있는 영역
  • pung list의 모든 데이터 관련 된 로직은 이 곳에서 처리

뷰 관련 - View

// VIEW ==============================================================================
.
..
...
    addView: function (viewData) {
        const { id, content, isToggle, timer } = viewData;
        const liElement = document.createElement("li");
        liElement.setAttribute("id", id);
        const template = `
            <div class="pungListContent">${content}</div>
            <div class="pungListTimer">${timer}초</div>
            <div class="pungListEtc">
                <button type="button" class="pungListTimerPlus">+5초</button>
                <button type="button" class="pungListToggle">${isToggle ? '시작' : '중지'}</button>
                <button type="button" class="pungListRemove">삭제</button>
            </div>
        `;

        liElement.innerHTML = template;

        const afterNodeIdx = this.pungLists.findIndex(x => x.id === id) + 1;
        const afterNodeID = this.pungLists[afterNodeIdx] && this.pungLists[afterNodeIdx].id;

        if (afterNodeID) {
            const afterNode = document.querySelector(`#${afterNodeID}`);
            this.pungListsElement.insertBefore(liElement, afterNode);
        }
        else {
            this.pungListsElement.appendChild(liElement);
        }

        this.setTimer(id, timer);
        this.bindEventNewList(id);
    },

    removeView: function (id) {
        const removeElement = document.querySelector(`#${id}`);

        this.pungListsElement.removeChild(removeElement);
    },

    updateView: function (id, timer) {
        const updateElement = document.querySelector(`#${id} .pungListTimer`);
        updateElement.textContent = `${timer}초`;

        this.setPungListAverge(timer);
    },

    toggleView: function (id, isToggle) {
        const toggleElement = document.querySelector(`#${id} .pungListEtc .pungListToggle`);

        if (isToggle) {
            toggleElement.textContent = "시작";
        }
        else {
            toggleElement.textContent = "중지";
        }
    },

    stopView: function (id) {
        const stopElement = document.querySelector(`#${id} .pungListEtc .pungListToggle`);
        stopElement.textContent = "시작";
    },

    startView: function (id) {
        const startElement = document.querySelector(`#${id} .pungListEtc .pungListToggle`);
        startElement.textContent = "중지";
    },
...
..
.
  • 처리된 데이터를 이용하여 실제 보여지는 UI를 처리하는 영역
  • 사용자의 액션에 의해 이벤트가 발생되면 데이터 변경 후 UI를 업데이트 함

이벤트 핸들러 관련 - Hnadler

// HANDLER ==============================================================================
.
..
...
	handleClickTimer: function (contentTarget, oEvent) {
        const { value } = contentTarget;
        const { target } = oEvent;
        const { second } = target.dataset;

        if (!value) {
            alert('내용을 입력해주세요!!');
            return;
        }

        this.add({ content: value, timer: Number(second), timerID: null, isToggle: false });
        contentTarget.value = "";
        contentTarget.focus();
    },

    handleClickTimerPlus: function (id) {
        this.update(id);
    },

    handleClickTimerToggle: function (id) {
        this.toggle(id);
    },
    
    handleClickTimerRemove: function (id) {
        this.remove(id);
    },

    handleClickListDoubleCopy: function () {
        let copyLists = [];
        copyLists = copyLists.concat(this.pungLists);

        copyLists.forEach(copyList => {
            this.add({ content: copyList.content, timer: copyList.timer, timerID: null, isToggle: false });
        });
    },

    handleClickSecondPlusAll: function () {
        this.pungLists.forEach(pungList => {
            this.update(pungList.id);
        });
    },

    handleClickStopAll: function () {
        this.pungLists.forEach(pungList => {
            this.stop(pungList.id);
        });
    },

    handleClickStartAll: function () {
        this.pungLists.forEach(pungList => {
            this.start(pungList.id);
        });
    },

    handleClickRemoveAll: function () {
        this.pungLists.forEach(pungList => {
            this.setTimerClear(pungList.timerID);
        });

        this.pungLists = []; // 초기화
        this.pungListsElement.innerHTML = "";

        this.setPungListCounter();
        this.setPungListAverge();
    },
...
..
.
  • 초기화에 필요한 이벤트 핸들러가 정의 되어 있는 영역

타이머 관련 - Timer

.
..
...
    setTimer: function (id, timer) {
        const targetList = this.getTarget(id);

        this.setPungListCounter();
        this.setPungListAverge(timer);

        const timerID = setInterval(() => {
            const listTimer = document.querySelector(`#${id} .pungListTimer`);

            if (timer < 2) {
                this.remove(id);
                this.setPungListAverge(timer);
                this.setPungListCounter();
                return;
            }

            timer--;
            listTimer.textContent = `${timer}초`;
            targetList.timer = timer;
            this.setPungListAverge(timer);
            this.setPungListCounter();
        }, 1000);

        targetList.timerID = timerID;
        targetList.timer = timer;
    },

    setTimerClear: function (timerID) {
        clearInterval(timerID);
        timerID = null;
    },

    setPungListCounter: function () {
        const countElement = document.querySelector("#pungCount");

        countElement.textContent = this.pungLists.length;
    },

    setPungListAverge: function () {
        const avergeElement = document.querySelector("#pungAverge");
        let averge;
        let avergeSum = 0;

        this.pungLists.forEach(pungList => {
            avergeSum += pungList.timer;
        });

        averge = avergeSum / this.pungLists.length;

        if(!averge) {
            averge = 0;        
        }

        avergeElement.textContent = averge.toFixed(1);
    },
...
..
.
  • 타이머 설정 및 해제, count, averge등을 정의하는 영역
  • element가 생성 될 때 타이머 설정이나 삭제 될 때 타이머 해제 등을 처리함

기타 - Etc

.
..
...
    getTarget: function (id) {
        return this.pungLists.filter(pungList => pungList.id === id)[0];
    },

    getTargetIdx: function (id) {
        return this.pungLists.findIndex(pungList => pungList.id === id);
    },
...
..
.
  • 데이터를 처리할 때 필요한 함수를 정의하는 영역
  • 공통적으로 사용되는 함수를 이곳에서 정의

2022.09.20
<전체삭제 / 전체+5초> 기능 오류 수정
handleClickRemoveAll 기능 추가적인 리팩토링 필요


전체코드
profile
잘하는것보다 꾸준히하는게 더 중요하다

0개의 댓글