Vanilla JS TodoList Project Version1

DW J·2022년 8월 18일
0

project-todolist

목록 보기
2/4

1. 프로젝트 파일 구조

todo-list
	ㄴ client
    	ㄴ index.html
    ㄴ css
    	ㄴ common.css
	ㄴ js
    	ㄴ todo_v1.js
	ㄴ server
  • 파일의 구조는 단순하게 하나의 파일에서 모든 기능 로직을 구현한다
  • server와 관련 된 로직은 client의 로직이 구현 된 후 진행한다
  • HTML과 CSS는 최대한 단순하게 작업한다

원래 최초에 계획했던 파일구조는 더 복잡한 형태의 파일구조 였습니다
CRM의 폴더구조 src + layout, page, component 등등...

처음부터 당연히 폴더구조를 잡고 작업을 진행하면 좋겠지만 그렇게 하지 않고 최대한 단순하게 시작해서 점점 발전시키는 형태로 작업을 해보고자 위와 같은 폴더구조로 작업을 진행하게 되었습니다


2. HTML / CSS

2-1) HTML

<div class="wrapper-todo">
	<div class="wrapper-todo-inner">
		<div class="todo-header">
			<div class="todo-title">
				<h2>TODO TITLE</h2>
            </div>
            <div class="todo-date">
            	<span class="todo-year"></span>
                <span class="todo-month"></span>
                <span class="todo-day"></span>
            </div>
        </div>
        <div class="todo-contents">
        	<div class="todo-add-contents">
        		<input type="text" class="form-control" id="input-box" />
        		<button type="button" class="btn" id="btn-add">+</button>
            </div>
            <ul>
            	<!-- 
                <li>
            		<input type="radio" />
                    <div>할일 할일 할일</div>
                   <span class="todo-remove">X</span>
                </li>
                <li>
                	<input type="radio" />
                	<div>
                		<input type="text" value="수정 모드 일때" />
               			수정 모드 일때
                    </div>
                    <span class="todo-remove">X</span>
                 </li>
                 -->
            </ul>
		</div>
	</div>
</div>
  • TodoList에서 layout을 담당하는 모든 HTML은 index.html내부에 태그로 존재한다
  • 데이터가 필요한 todolist(ul부분)에만 들어갈 li태그의 내용을 넣어놓고 js에서 구현한다

입력과 추가, 삭제, 수정의 기능만 할 수 있게 최대한 간단하게 HTML을 구성해 보았습니다 우선 할일목록(데이터)이 들어 갈 li태그만 js에서 처리하도록 하고 나머지 태그는 index.html내부에 존재하도록 하였습니다

2-2) CSS

* {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    padding: 0;
    height: 100%;
}

.show {
    display: block;
}

.hide {
    display: none;
}

.disabled {
    background-color: #e1e1e1;
}

.disabled:hover {
    cursor: no-drop !important;
}

.wrapper-todo {
    min-height: 300px;
    max-width: 600px;
    background-color: #fff;
    border: 1px solid #c9c9c9;
    border-radius: 5px;
    padding: 20px;
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    color: #333;
    margin: 50px auto 0;
}

/* HEADER */
.todo-header .todo-title {
    text-align: center;
}

.todo-header .todo-title h2 {
    margin: 0;
    font-size: 2rem;
}

.todo-header .todo-date-contents {
    display: flex;
    justify-content: center;
    margin-top: 20px;
}

.todo-header .todo-date-contents span {
    margin: 0 5px;
    font-size: 1.2rem;
}

/* CONTENTS */
.todo-contents {
    margin-top: 30px;
}

.todo-contents .todo-add-contents {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    position: relative;
}

.todo-contents .todo-add-contents .add-input {
    box-sizing: border-box;
    padding: 7px;
    height: 40px;
    border: 1px solid #c9c9c9;
    border-radius: 3px 0 0 3px;
    flex: 1;
    margin-right: 69px;
}

.todo-contents .todo-add-contents .add-button {
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    width: 70px;
    border: 1px solid #c9c9c9;
    border-radius: 0 3px 3px 0;
    font-size: 1.5rem;
    color: #666;
    cursor: pointer;
}

.todo-contents ul {
    list-style: none;
    padding: 0;
    margin: 0;
}

.todo-contents ul li {
    display: flex;
    align-items: center;
    padding: 20px;
    border-bottom: 1px solid #ebebeb;
}

.todo-contents ul li:hover {
    cursor: pointer;
}

.todo-contents ul li input[type="checkbox"] {
    margin: 0;
}

.todo-contents ul li div {
    flex: 1 1 0;
    margin-left: 10px;
    margin-right: 10px;
    font-size: 1.1rem;
}

.todo-contents ul li div input[type="text"] {
    width: 100%;
    font-size: 1rem;
    border: 1px solid #c9c9c9;
    padding: 6px;
}

.todo-contents ul li span {
    font-size: 1.5rem;
    color: #999;
    padding: 0px 10px;
    cursor: default;
}

.todo-contents ul li.disabled div {
    text-decoration: line-through;
}
  • HTML과 마찬가지로 CSS또한 최대한 심플하게 작업을 진행한다

CSS또한 TodoList가 화면에 이쁘게만(?)보여질 수 있도록 작업하였습니다


3. JS

const todo = () => {
	... 내부 로직
}

window.addEventListener("load", todo);
  • 하나의 함수 내부에서 모든 로직을구현
  • window load이벤트 콜백으로 함수 추가

처음부터 하나의 파일에서 모든 로직 구현 > 기능/역할에 맞게 분리 하는것이 프로젝트의 목표였기때문에 기능 구현에만 초점을 두고 작업을 진행했습니다.

3-1) JS 전체로직

const todo = () => {
    // TODOLIST 관련
    const inputBox = document.querySelector(".add-input");
    const addBtn = document.querySelector(".add-button");
    const todoList = document.querySelector(".todos");

    // EVENT
    const eventHandler = {
        // UPDATE
        handleUpdate: (e) => {
            console.log("수정");
            const { target } = e;
            const parent = target.closest("li");

            if (target.tagName !== "INPUT" && parent.classList.value !== "disabled") {
                const value = target.textContent;
                const input = target.querySelector("input");
                target.textContent = "";

                input.value = value;
                input.style.display = "block";
                target.appendChild(input);
                input.focus();
            }
        },

        // UPDATE COMPLETE
        handleUpdateComplete: (e) => {
            console.log("수정완료");

            const { target } = e;
            const parent = target.parentElement;
            let value = target.value;

            if (!value) {
                if (target.style.display === "none") {
                    target.style.display = "block";
                    target.focus();
                }
                console.log("내용을 입력해주세요.");
                return false;
            }

            const textNode = document.createTextNode(value);

            target.style.display = "none";
            parent.appendChild(textNode);
        },

        // COMPLETE
        handleComplete: (e) => {
            console.log("완료");
            const { target } = e;
            const completeTarget = target.closest("li");

            if (completeTarget.classList.value === "disabled") {
                completeTarget.classList.remove("disabled");
                todoList.prepend(completeTarget)
                target.checked = false;
            }
            else {
                completeTarget.classList.add("disabled");
                todoList.appendChild(completeTarget);
            }
        },

        // REMOVE
        handleRemove: (e) => {
            console.log("삭제");
            const removeTarget = e.target.closest("li");
            todoList.removeChild(removeTarget);
        }
    }

    // ADD ITEM
    const addItem = () => {
        if (inputBox.value === "") {
            console.log("내용을 입력해주세요.");
            return false;
        }

        console.log("추가");

        const liElement = document.createElement("li");

        const radioElement = document.createElement("input");
        radioElement.setAttribute("type", "radio");
        radioElement.addEventListener("click", eventHandler.handleComplete); // 완료

        const divElement = document.createElement("div");
        divElement.innerText = inputBox.value;
        divElement.addEventListener("click", eventHandler.handleUpdate); // 수정

        const updateInput = document.createElement("input");
        updateInput.setAttribute("type", "text");
        updateInput.style.display = "none";
        updateInput.innerText = "";
        updateInput.addEventListener("focusout", eventHandler.handleUpdateComplete); // 수정완료
        updateInput.addEventListener("keypress", (e) => {
            if (e.keyCode === 13) {
                // 강제로 포커스를 제거하여 focusout event를 발생시킨다
                const { target } = e;
                target.blur(); // focusout 이벤트 실행을 위해 포커스를 제거하고..
                target.focus(); // 포커스를 다시 준다
            }
        });

        const spanElement = document.createElement("span");
        spanElement.classList.add("todo-remove");
        spanElement.innerText = "X";
        spanElement.addEventListener("click", eventHandler.handleRemove); // 삭제

        divElement.appendChild(updateInput);

        liElement.appendChild(radioElement);
        liElement.appendChild(divElement);
        liElement.appendChild(spanElement);

        // TODO - disabled로 변경..
        const completeList = todoList.querySelector(".disabled");

        if (completeList) {
            todoList.insertBefore(liElement, completeList);
        }
        else {
            todoList.appendChild(liElement);
        }

        inputBox.value = "";
    }

    inputBox.addEventListener("keypress", (e) => {
        if (e.keyCode === 13) {
            addItem();
        }
    });

    addBtn.addEventListener("click", (e) => {
        addItem();
    });

    // TODODATE 관련
    const todoYear = document.querySelector(".todo-year");
    const todoMonth = document.querySelector(".todo-month");
    const todoDay = document.querySelector(".todo-day");
    const getTodayDate = () => {
        const date = new Date();
        let yaer = date.getFullYear().toString();
        let month = date.getMonth() + 1;
        let day = date.getDay();

        return {
            yaer: yaer,
            month: (month >= 10) ? month : "0" + month,
            day: (day >= 10) ? day : "0" + day
        };
    };
    const currentDate = getTodayDate();
    todoYear.innerText = currentDate.yaer + "년";
    todoMonth.innerText = currentDate.month + "월";
    todoDay.innerText = currentDate.day + "일";
}

window.addEventListener("load", todo);
  • 함수 내부 전체에서 사용 할 전역 변수 정의
  • 목록을 추가할 때 필요한 element를 만들고 속성과 이벤트를 설정
  • 이벤트와 관련 된 로직은 이벤트 객체 내에 구현
  • 오늘 날짜를 표시해주는 로직 추가

새로운 할일목록은 상단에 위치한 인풋박스(keypress)와 버튼(click) 각각에 이벤트를 설정하여 구현하였고 할일목록이 생성될 때 각각의 element를 추가하고 수정, 삭제, 완료 등 이벤트를 추가하였습니다

전체 코드 - https://github.com/jungcolor/app_project/tree/main/todo-list/client


Version1을 마치며..

TodoList를 처음 만들어보며 이런저런 고민을 많이 했습니다.

그간 회사나 커뮤니티, 또는 인터넷에서 보고 배웠던 기술들을 어떤식으로 프로젝트에 적용할 수 있을까 라는 고민부터 그렇게 하려면 어떤식으로 진행을 해야 할지... 하지만 이미 구축되어 있는 프레임워크에서 유지보수와 기능을 추가하는 것을 해왔던 저로써는 시작도 하기전에 고민만 하는 상황이 지속되었고 포기할거 같아 우선 내가 알고 있는 지식들로 만들기 시작했습니다.

폴더를 만들고 파일을 만들었다 지웠다, 파일이름을 변경했다가 다시 재변경 하고 로직을 이리저리 옮겨보기도 하고 이게 맞나? 이건 틀리지 않았나? 라는 고민과 시행착오를 겪으면서 제가 의도한 대로 동작하는걸 보고 성취감과 개발에 대한 재미를 느끼고 있습니다.

하지만 이 상태로 만족하지 않습니다. 제 스스로가 만족 할 수 있을만한 코드가 될 때까지 이리저리 부딫히며 열심히 달려볼 생각입니다

앞으로의 목표는 최고의 개발자가 되겠다는 생각보다, 꾸준히 열심히 하여 열정있는 개발자가 되고자 합니다.

감사합니다.

profile
잘하는것보다 꾸준히하는게 더 중요하다

0개의 댓글