todo-list
ㄴ client
ㄴ index.html
ㄴ css
ㄴ common.css
ㄴ js
ㄴ todo_v1.js
ㄴ server
원래 최초에 계획했던 파일구조는 더 복잡한 형태의 파일구조 였습니다
CRM의 폴더구조 src + layout, page, component 등등...
처음부터 당연히 폴더구조를 잡고 작업을 진행하면 좋겠지만 그렇게 하지 않고 최대한 단순하게 시작해서 점점 발전시키는 형태로 작업을 해보고자 위와 같은 폴더구조로 작업을 진행하게 되었습니다
<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>
입력과 추가, 삭제, 수정의 기능만 할 수 있게 최대한 간단하게 HTML을 구성해 보았습니다 우선 할일목록(데이터)이 들어 갈 li태그만 js에서 처리하도록 하고 나머지 태그는 index.html내부에 존재하도록 하였습니다
* {
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;
}
CSS또한 TodoList가 화면에 이쁘게만(?)보여질 수 있도록 작업하였습니다
const todo = () => {
... 내부 로직
}
window.addEventListener("load", todo);
처음부터 하나의 파일에서 모든 로직 구현 > 기능/역할에 맞게 분리 하는것이 프로젝트의 목표였기때문에 기능 구현에만 초점을 두고 작업을 진행했습니다.
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);
새로운 할일목록은 상단에 위치한 인풋박스(keypress)와 버튼(click) 각각에 이벤트를 설정하여 구현하였고 할일목록이 생성될 때 각각의 element를 추가하고 수정, 삭제, 완료 등 이벤트를 추가하였습니다
전체 코드 - https://github.com/jungcolor/app_project/tree/main/todo-list/client
TodoList를 처음 만들어보며 이런저런 고민을 많이 했습니다.
그간 회사나 커뮤니티, 또는 인터넷에서 보고 배웠던 기술들을 어떤식으로 프로젝트에 적용할 수 있을까 라는 고민부터 그렇게 하려면 어떤식으로 진행을 해야 할지... 하지만 이미 구축되어 있는 프레임워크에서 유지보수와 기능을 추가하는 것을 해왔던 저로써는 시작도 하기전에 고민만 하는 상황이 지속되었고 포기할거 같아 우선 내가 알고 있는 지식들로 만들기 시작했습니다.
폴더를 만들고 파일을 만들었다 지웠다, 파일이름을 변경했다가 다시 재변경 하고 로직을 이리저리 옮겨보기도 하고 이게 맞나? 이건 틀리지 않았나? 라는 고민과 시행착오를 겪으면서 제가 의도한 대로 동작하는걸 보고 성취감과 개발에 대한 재미를 느끼고 있습니다.
하지만 이 상태로 만족하지 않습니다. 제 스스로가 만족 할 수 있을만한 코드가 될 때까지 이리저리 부딫히며 열심히 달려볼 생각입니다
앞으로의 목표는 최고의 개발자가 되겠다는 생각보다, 꾸준히 열심히 하여 열정있는 개발자가 되고자 합니다.
감사합니다.