To do List를 HTML, CSS, JavaScript를 이용해서 만들어보다.
HTML, JS
<!DOCTYPE html>
<html lang="kr">
<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>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Nanum+Gothic:800" rel="stylesheet">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<h1>Todo List</h1>
<div class="input-area">
<input type="text" id="task-input">
<button class="btn btn-primary" id="add-button">추가</button>
</div>
<div class="task-area">
<div id="task-board">
</div>
</div>
</div>
<!-- JavaScript -->
<script>
// Step 1. "추가" 버튼을 클릭했을 때, taskList에 할일 추가하기
// console창에만
let userInput = document.getElementById("task-input");
let addBtn = document.getElementById("add-button");
let taskBoard = document.getElementById("task-board");
let sucBtn = document.getElementById("btn btn-success")
let danBtn = document.getElementById("btn btn-danger")
let task = {};
let taskList = [];
const addTask = () => { // 할일을 추가하는 함수
// console.log("유저 입력값: ", userInput.value)
// 1 - 2) 사용자가 input에 작성한 값을 기반으로 task 객체를 선언
// 이 때, task객체에는 id(구별을 위한 고유한 값),
// taskContent(할 일 input값), isCompleted(완료 여부 true or false)
task.taskContent = userInput.value // 유저의 입력값 받기
task.id = randomIdGenerator()
task.isCompleted = false;
// console.log("task 객체: ", task)
// 1- 4) 배열 taskList를 선언해서 그 안에 새로운 task객체를 추가한다.
// 단 마지막 인덱스에 추가할 것.
taskList.push(task) // 배열에 객체 넣기
// 1 - 5) console창에 할일이 추가되어있는 새로운 배열을 확인해보자
console.log("taskList: ", taskList)
// 1 - 6) 1 - 5 까지 성공하면, input 안에 있는 값을 비워줄 것.
task = {}; // 객체를 초기화하여 배열에 새로운 객체 추가하기 위함
userInput.value = ""; // 추가 버튼 누른 후 입력창 초기화
userInput.focus() // 입력 후 입력창 자동 클릭
// Step 2. 바뀐 값을 기준으로 화면을 렌더링해보자
render()
}
// 1 - 3) id 값은 고유한 값이어야 함.
const randomIdGenerator = () => {
return "_" + Math.random().toString(36).substring(2, 11) // toString(36) 알파벳 생성
}
// 현재 taskList를 기준으로 화면을 갱신해주는 함수
// - 왜 굳이 render함수를 따로 뽑아서 사용할까? -> 등록, 삭제 ,완료 모두 사용!
// 재사용성 때문
const render = () => {
// 2 - 1) taskList 배열 안에 있는 값을 for문으로 가져와서 할일의 이름을
// console.log("할 일: ", taskList[i].taskContent)
// console 창에 찍어보자
let resultCode = "";
for (i = 0; i <= taskList.length - 1; i++) {
// 3-7) 만약에 taskList의 isCompleted 속성이 true라면 task-done
// 그렇지 않다면 task
if(taskList[i].isCompleted === true){
resultCode += `<div class="task task-done">
<span>${taskList[i].taskContent}</span>
<div class="button-area">
<button class="btn btn-success" onclick= "toggleComplete('${taskList[i].id}')">완료</button>
<button class="btn btn-danger" onclick="delTask('${taskList[i].id}')">삭제</button>
</div>
</div>`}
else{
resultCode += `<div class="task">
<span>${taskList[i].taskContent}</span>
<div class="button-area">
<button class="btn btn-success" onclick= "toggleComplete('${taskList[i].id}')">완료</button>
<button class="btn btn-danger" onclick="delTask('${taskList[i].id}')">삭제</button>
</div>
</div>`
}
// 2 - 2) 2-1에서 가져온 값들을 taskBoard안에 나열해보자
// tip! resultCode 라는 변수 안에 코드를 누적시키고,
// 마지막에 resultCode만 노출되게 해보자
// resultCode를 사용해서
// 2 - 3) 기존에 있었던 디자인까지 그대로 입혀보자.
// resultCode += `<div class="task">
// <span>${taskList[i].taskContent}</span>
// <div class="button-area">
// <button class="btn btn-success" onclick= "toggleComplete('${taskList[i].id}')">완료</button>
// <button class="btn btn-danger">삭제</button>
// </div>
// </div>`
}
taskBoard.innerHTML = resultCode
/*
<div class="task task-done">
<span>운동하기</span>
<div class="button-area">
<button class="btn btn-success">완료</button>
<button class="btn btn-danger">삭제</button>
</div>
</div>
<div class="task">
<span>요리하기</span>
<div class="button-area">
<button class="btn btn-success">완료</button>
<button class="btn btn-danger">삭제</button>
</div>
</div>
*/
}
// Step 3. "완료" 버튼 클릭 시, 할일을 완료로 처리
// 3-1) render 함수 안에 있는 완료 button에 직접 onclick이벤트를 걸어준다.
const toggleComplete = (id) => {
// 3-2) 클릭이벤트를 구분하는 고유한 값인 id를 콘솔창에 찍어주기
console.log("complete id: ", id)
// 3-3) 현재 받아온 id값과 동일한 id 값을 taskList에서 찾아보기.
for(i = 0; i < taskList.length; i++){
if(id === taskList[i].id){
// 3-4) 일치할 경우 ,해당 아이템의 isCompleted 속성을 가진 값과 반대로 표시
// tip !true = false, !false = true
taskList[i].isCompleted = !taskList[i].isCompleted
}
}
// 3-5) 3-4까지 마무리 됐다면 taskList를 콘솔창에 출력해서 해당 아이템의 isCompleted속성이 바뀌었는지 확인
console.log(taskList)
render()
}
// Step 4. 삭제 버튼 클릭 시, 할일을 삭제
const delTask = (id) => {
console.log("del Task", id)
for(i = 0; i < taskList.length; i++){
if(taskList[i].id === id){
taskList.splice(i, 1)
}
}
render()
}
// 1 - 1) "추가" 버튼을 클릭 했을 때, input 안에 작성한 값을 console창에 확인
addBtn.addEventListener("click", addTask)
</script>
</body>
</html>
CSS
body{
background-image: url(https://img.freepik.com/free-vector/hand-painted-watercolor-pastel-sky-background_23-2148902771.jpg?w=2000);
background-repeat: no-repeat;
background-size: cover;
font-family: "Nanum Gothic";
}
.container{
background-color: white;
/* vh: viewport height로 현재 브라우저 높이를 꽉 채울 때 100vh를 사용 */
height: 100vh;
box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, rgba(0, 0, 0, 0.06) 0px 0px 0px 1px;
padding: 70px;
overflow: auto;
}
.input-area{
display: flex;
margin-bottom: 10px;
}
#add-button{
margin-left: 5px;
}
.task-tabs{
display: flex;
border: 1px solid gray;
}
.task-tabs > div{
padding: 10px;
cursor: pointer;
}
.task-tabs > div:hover{
background-color: #00a8ff;
color:white;
}
.task-area{
position: relative;
}
.task{
display: flex;
justify-content: space-between;
position: relative;
width: 100%;
border-radius: 10px;
margin: 15px 0;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
}
.task span{
box-sizing: border-box;
padding: 10px;
line-height: 38px;
}
.task-done{
background-color: lightgray;
}
.task-done > span{
text-decoration: line-through;
}
.button-area{
display: flex;
padding: 10px 10px 10px 0;
background-color: white;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.button-area > button{
margin-left: 10px;
}
1. 변수 선언 및 DOM
// Step 1. "추가" 버튼을 클릭했을 때, taskList에 할일 추가하기
// console창에만
let userInput = document.getElementById("task-input");
let addBtn = document.getElementById("add-button");
let taskBoard = document.getElementById("task-board");
let sucBtn = document.getElementById("btn btn-success")
let danBtn = document.getElementById("btn btn-danger")
let task = {};
let taskList = [];
HTML 요소를 가져오고 task 객체와 taskList 배열을 선언해 준다. 유저가 입력한 입력값의 속성과 속성값을 객체에 보관하기 위해 task 빈 객체를 선언하고, 그 객체들을 배열에 보관해서 사용자에게 보이게 하기 위해서 taskList 빈 배열을 선언한다.
2. 할 일을 추가하기
// 1 - 1) "추가" 버튼을 클릭 했을 때, input 안에 작성한 값을 console창에 확인
addBtn.addEventListener("click", addTask)
// 1 - 3) id 값은 고유한 값이어야 함.
const randomIdGenerator = () => {
return "_" + Math.random().toString(36).substring(2, 11) // toString(36) 알파벳 생성
}
const addTask = () => { // 할일을 추가하는 함수
// console.log("유저 입력값: ", userInput.value)
// 1 - 2) 사용자가 input에 작성한 값을 기반으로 task 객체를 선언
// 이 때, task객체에는 id(구별을 위한 고유한 값),
// taskContent(할 일 input값), isCompleted(완료 여부 true or false)
task.taskContent = userInput.value // 유저의 입력값 받기
task.id = randomIdGenerator()
task.isCompleted = false;
// console.log("task 객체: ", task)
// 1- 4) 배열 taskList를 선언해서 그 안에 새로운 task객체를 추가한다.
// 단 마지막 인덱스에 추가할 것.
taskList.push(task) // 배열에 객체 넣기
// 1 - 5) console창에 할일이 추가되어있는 새로운 배열을 확인해보자
console.log("taskList: ", taskList)
// 1 - 6) 1 - 5 까지 성공하면, input 안에 있는 값을 비워줄 것.
task = {}; // 객체를 초기화하여 배열에 새로운 객체 추가하기 위함
userInput.value = ""; // 추가 버튼 누른 후 입력창 초기화
userInput.focus() // 입력 후 입력창 자동 클릭
render() // 렌더링
}
추가 버튼을 클릭했을 때 이벤트를 감지하기 위해 이벤트 리스너를 걸어준다.
addTask 함수는 클릭했을 때 실행되는 함수이고, 기능은 입력 값의 텍스트, id, 완료 상태를 속성으로 하고 그 각각의 값을 task 객체에 넣는 역할을 한다. 그 task를 taskList배열에 넣기까지 한다. 텍스트는. value로 추출하고, 완료 상태는 할 일을 추가했을 때는 미완 상태이기 때문에 false로 주었다. id를 주는 이유는 텍스트로 할 일들을 구분하려고 하면 같은 이름을 가진 할 일들을 컴퓨터는 구별할 수 없기 때문에 컴퓨터를 위해 id 값을 랜덤으로 생성해서 할 일들이 입력될 때마다 그 할 일에 id 값을 준다. id로 비교할 것이다. 나는 객체이름. 속성 = 속성값 방법으로 대입했다. 이 과정까지만 하면 첫 번째 할 일을 잘 배열에 들어가지만 두 번째 할 일부터 잘 안된다. 첫 번째 할 일이 사라지고 배열에 들어간다. 그래서 할 일을 1번 입력받으면 객체를 초기화시켜서 새로운 할 일을 받을 빈 객체로 만들어주는 코드를 하단에 작성했다. 부가적인 설명은 코드의 주석 확인가장 하단의 render()는 화면에 보이게 해주는 함수로 이후에 설명한다.
2. render( ) / 화면에 보이게 하는 함수
// 현재 taskList를 기준으로 화면을 갱신해주는 함수
// - 왜 굳이 render함수를 따로 뽑아서 사용할까? -> 등록, 삭제 ,완료 모두 사용!
// 재사용성 때문
// Step 2. 바뀐 값을 기준으로 화면을 렌더링해보자
const render = () => {
// 2 - 1) taskList 배열 안에 있는 값을 for문으로 가져와서 할일의 이름을
// console.log("할 일: ", taskList[i].taskContent)
// console 창에 찍어보자
let resultCode = "";
for (i = 0; i <= taskList.length - 1; i++) {
// 3-7) 만약에 taskList의 isCompleted 속성이 true라면 task-done
// 그렇지 않다면 task
if(taskList[i].isCompleted === true){
resultCode += `<div class="task task-done">
<span>${taskList[i].taskContent}</span>
<div class="button-area">
<button class="btn btn-success" onclick= "toggleComplete('${taskList[i].id}')">완료</button>
<button class="btn btn-danger" onclick="delTask('${taskList[i].id}')">삭제</button>
</div>
</div>`}
else{
resultCode += `<div class="task">
<span>${taskList[i].taskContent}</span>
<div class="button-area">
<button class="btn btn-success" onclick= "toggleComplete('${taskList[i].id}')">완료</button>
<button class="btn btn-danger" onclick="delTask('${taskList[i].id}')">삭제</button>
</div>
</div>`
}
// 2 - 2) 2-1에서 가져온 값들을 taskBoard안에 나열해보자
// tip! resultCode 라는 변수 안에 코드를 누적시키고,
// 마지막에 resultCode만 노출되게 해보자
// resultCode를 사용해서
// 2 - 3) 기존에 있었던 디자인까지 그대로 입혀보자.
// resultCode += `<div class="task">
// <span>${taskList[i].taskContent}</span>
// <div class="button-area">
// <button class="btn btn-success" onclick= "toggleComplete('${taskList[i].id}')">완료</button>
// <button class="btn btn-danger">삭제</button>
// </div>
// </div>`
}
taskBoard.innerHTML = resultCode
/*
<div class="task task-done">
<span>운동하기</span>
<div class="button-area">
<button class="btn btn-success">완료</button>
<button class="btn btn-danger">삭제</button>
</div>
</div>
<div class="task">
<span>요리하기</span>
<div class="button-area">
<button class="btn btn-success">완료</button>
<button class="btn btn-danger">삭제</button>
</div>
</div>
*/
}
사용자에게 보이게 해야 하니까 HTML 문법을 추가해줘야 한다. 새로운 변수 resultCode에 태그 뭉탱이(디자인된 거)를 누적해 줄 것이다. render( ) 밖에서 할 일 리스트가 나열되는 곳의 요소에 innerHTML을 사용해서 넣기 때문에 화면에 반영된다.
taskList는 배열이고 배열에 있는 객체들을 하나하나 isCompleted가 true인지 false 인지 확인해 주면 된다. 배열 전부를 반복하는 방법은 지겹도록 많이 했기 때문에 설명하지 않겠다.
isCompleted 속성값이 true이면 완성 버튼을 누르고 완성됐다는 것이다. 완성되면 div태그의 class에 task-done이 추가된다. span태그 콘텐츠는 사용자가 입력한 값(task.Content에 저장했음)을 넣어야 하므로 taskList [인덱스]. taskContent를 넣어주면 된다.
3. 확인 버튼 활성화
// Step 3. "완료" 버튼 클릭 시, 할일을 완료로 처리
// 3-1) render 함수 안에 있는 완료 button에 직접 onclick이벤트를 걸어준다.
const toggleComplete = (id) => {
// 3-2) 클릭이벤트를 구분하는 고유한 값인 id를 콘솔창에 찍어주기
console.log("complete id: ", id)
// 3-3) 현재 받아온 id값과 동일한 id 값을 taskList에서 찾아보기.
for(i = 0; i < taskList.length; i++){
if(id === taskList[i].id){
// 3-4) 일치할 경우 ,해당 아이템의 isCompleted 속성을 가진 값과 반대로 표시
// tip !true = false, !false = true
taskList[i].isCompleted = !taskList[i].isCompleted
}
}
// 3-5) 3-4까지 마무리 됐다면 taskList를 콘솔창에 출력해서 해당 아이템의 isCompleted속성이 바뀌었는지 확인
console.log(taskList)
render()
}
과정 2와 번갈아봐야 한다.
버튼 자체에 onclick 이벤트를 걸어서 어떤 요소가 클릭 됐는지 감지하게 해 준다. 과정 2에서 확인 버튼을 클릭했을 때 실행되는 함수가 toggleComplete( )이다. 과정 3에서 이 함수의 기능을 만들어줄 것이다. 무엇을 해야 하냐면 확인 버튼을 눌렀을 때 isCompleted가 true가 되어야 한다. 그전에 사용자가 클릭한 버튼을 포함한 할 일의 고유한 id값을 이용할 것이다.
과정 2에서 "toggleComplete('${taskList [i]. id}')"를 썼다. 괄호 안에서 '(작은따옴표)를 넣는 이유는 안 넣으면 id defined 오류가 난다. 아무튼 id값이 일치할 때 false를 true로, true를 false로 만들어주기 위해서 !를 사용해서 바꿨다.
마지막에 render함수를 넣어서 바로 화면에 출력되게 해준다. 그럼 render함수 안에서 true가 된 것들은 완료 처리가 돼서 class에 task-done이 추가될 것이다.
4. 삭제 버튼 활성화
// Step 4. 삭제 버튼 클릭 시, 할일을 삭제
const delTask = (id) => {
console.log("del Task", id)
for(i = 0; i < taskList.length; i++){
if(taskList[i].id === id){
taskList.splice(i, 1)
}
}
render()
}
삭제 버튼의 기능은 버튼을 클릭했을 때 배열에서 그 객체가 삭제되는 것이다. 배열에서 삭제된다는 것은 화면에 출력되는 리스트에서도 삭제된다는 것을 의미한다. 그럼 또 배열의 객체들의 id들과 삭제 버튼을 클릭한 것의 id와 비교해서 같은 것을 배열에서 삭제하면 된다. 배열에서 원하는 위치에 있는 것을 삭제하는 배열함수는 splice(시작인덱스, 이후로 몇 개)이다.
마지막에 render함수를 넣어서 바로 화면에 출력될 수 있게 해 준다.
배운 점
- render함수를 따로 만들어서 클릭했을 때 함수 안이 깔끔해지고 재사용성이 좋아졌다.
- 이벤트 리스너를 통해 클릭 이벤트만 넣다가 같은 기능을 하는 버튼이 여러 개일 때는 버튼에 직접 onclick을 넣어주는 방법이 좋았다.
- 랜덤으로 만든 고유한 id로 입력값끼리 구별하는 방법이 새로웠다.
'Front-End > 3. JavaScript 실전' 카테고리의 다른 글
[JS] 정규표현식 기호, 메서드 (Regex : Regular Expression) (1) | 2023.11.28 |
---|---|
[JS] 객체 - key 값에 대괄호를 씌우는 이유 (4) | 2023.11.22 |
[JS] Ex14_숫자맞추기 실습.html (선생님 코드.ver) (0) | 2023.06.21 |
[JS] Ex14_숫자맞추기 실습.html (실패한 코드.ver) (0) | 2023.06.20 |
[JS] Ex13_DOM스타일.html (0) | 2023.06.20 |