-
todolist 또 만들기만든 화면 찌끄리기 2024. 8. 2. 10:00
로컬스토리지를 이용해 투두리스트를 또! 만들었다.
사실 블로그에 todolist는 이름의 포스팅은 두 번째지만 회원가입 포스팅도 사실 투두리스트를 생각하고 만들었다는 사실ㅋ
근데 왜 몇 번을 해도 술술 써 내려가지 못하는 걸까.. 아무튼 이번 todolist도 이전에 작업했던 것들을 참고해서 만들었다.
이번 콘셉은 메모장에 메모지를 붙여놓은 듯한 디자인으로 만들었다. 뭐.. 디자이너분들이 혹시 본다면 별거 아닐 수 있겠지만 매우 똥손인 나에게는 정말 잘 만들었다고 자화자찬하는 중이다.
HTML
<div class="container"> <div class="notepad"> <button type="button" class="new-memo-btn"></button> <form action="#" class="write-note"> <input type="text" class="title" placeholder="할 일 타이틀"> <textarea class="text" placeholder="할 일 작성"></textarea> <button type="submit" class="add-btn">add</button> </form> <p class="main-title">- 메모</p> <ul class="memo-box"> <!-- 메모를 찌그려보세요. --> </ul> <div class="black-bg"></div> </div> </div>
사실 HTML은 별거 없다. 이게 전부다.
그리고 CSS는 너무 길어서 따로 올리지 않으려고 한다.
전체적인 그림은 이렇다.
배경과 연필 이미지는 그냥 구글에 돌아다니는 이미지를 사용했고. 폰트는 구글폰트에서 최대한 손글씨 같은 폰트로 선택했다. 콘셉유지!
메모 추가하기
function addMemo(){ // 메모 추가하기 const title = document.querySelector('.title'); // 타이틀 입력 const text = document.querySelector('.text'); // 텍스트 입력 const currentDate = new Date(); // 현재 시간 const numberDate = Number(currentDate) // 현재 시간 Number const year = currentDate.getFullYear(); // 년도 const month = currentDate.getMonth() + 1; // 월 const date = currentDate.getDate(); // 일 let memoInfo = { id: numberDate, checked: '', title: title.value, text: text.value, creatDate: `${year}.${month}.${date}`, } if (title.value === '') { alert('메모 타이틀을 입력하세요.'); } else if (text.value === '') { alert('메모 텍스트를 입력하세요.'); } else { memos.push(memoInfo); // memos 배열에 memoInfo 객체 넣기 title.value = ''; text.value = ''; } createMemo(); saveMemo(); }
메모를 추가하는 함수이다.
memoInfo라는 객체에 id값은 각각의 체크박스들의 HTML에서 체크 상태를 제어하기 위해 new Date()를 사용했다.
타이틀 값과 상세 내용에 대한 텍스트 값을 받아내고 또 new Date()의 년/월/일 을 따로 받아내서 메모가 작성되는 날짜를 표시해 줄 수 있도록 만들었다.
또 체크박스의 체크상태를 로컬스토리지에 저장하기 위해 checked에 빈 문자열을 넣었고 체크박스가 체크가 될 때마다 객체에서 checked: 'checked'가 되게끔 만들었다.
checked 같은 경우는 처음에 disabled=" true/false "처럼 하면 될 거라고 생각했는데 checked는 true/false로 설정하려면 JavaScript로 제어하는 방법밖에는 찾지 못했다.
메모를 추가하는 함수는 시작할 때 했던 작업으로 로직이 길지 않기 때문에 딱히 어려움은 없었다.
메모 생성하기
const pastelColors = [ '#E0BBE4', // 연한 보라 '#BFE1B0', // 연한 녹색 '#FFD8B1', // 연한 복숭아 '#B2E0D8', // 연한 민트 '#FFC0CB', // 연한 핑크 '#D7A9E3', // 연한 라벤더 '#E9F7EF', // 연한 라임 '#C2F0C2', // 연한 연녹색 '#B4E2D5', // 연한 하늘색 '#F6BDC0' // 연한 코랄 ]; function createMemo(){ // 메모 생성하기 const memoBox = document.querySelector('.memo-box'); memoBox.innerHTML = memos.map((memo) => { const randomIndex = Math.floor(Math.random() * pastelColors.length); const randomColor = pastelColors[randomIndex]; const randomRotation = (Math.random() * 10) - 5; // -5~5도의 랜덤 각도 return ( ` <li class="memo-list"> <div class="memo-paper" style="background-color: ${randomColor}; transform: rotate(${randomRotation}deg);"> <span class="chk-box"> <input type="checkbox" class="memo-check" name="memo-check" id="${memo.id}" ${memo.checked}> <label for="${memo.id}"> <span class="date">${memo.creatDate}</span> <span class="title"> ${memo.title} <input type="text" class="edit-title" value="${memo.title}"> </span> <span class="check-icon"></span> </label> </span> <span class="read-memo">${memo.text}</span> <textarea class="edit-memo">${memo.text}</textarea> <span class="btn-box"> <button class="close-btn"></button> <div class="center-sort"> <button type="button" class="save-btn">save</button> <button type="button" class="edit-btn">edit</button> <button type="button" class="del-btn">delete</button> </div> </span> </div> </li> ` ) }).join(''); delMemo(); editMemo(); checkMemo(); }
메모지의 색상이 여러 개가 있는 것 같은 표현을 하고 싶어서 연한 파스텔톤 색상을 몇 개 뽑았고 적용했고 메모지를 대충 붙인 느낌으로 하고 싶어서 각각의 메모지에 -5도~5도의 랜덤 한 각도를 적용했다.
페이지가 로드될 때마다 메모지의 색상과 각도가 바뀐다. 그리고 innerHTML과 백틱(`)으로 메모 리스트의 구조를 작성했고 각도와 색상은 css파일이 아닌 태그 자체에 style에서 랜덤 하게 적용되게끔 만들었다.
어려웠던 점이라고 할 건 없긴 했는데 파스텔톤 컬러 코드와 각도를 구하는 값은 GPT를 사용해서 받아냈다.
페이지가 로드되면 화면에 표시하기
function saveMemo(){ // 로컬 스토리지에 저장 const stringifyMemo = JSON.stringify(memos); // memos 배열에 있는 값을 문자열로 변환 localStorage.setItem('myMemo', stringifyMemo); // 문자열로 변환된 memos를 myMemo이름으로 로컬 스토리지에 저장 } function loadMemo(){ // 화면에 표시하기 const localGetMemo = localStorage.getItem('myMemo'); // 로컬 스토리지에서 myMemo 받아오기 if (localGetMemo !== null) { const parseMemo = JSON.parse(localGetMemo); // 로컬 스토리지에서 받아온 myMemo를 JavaScript에서 사용 할 수 있게 변환 memos = parseMemo; // memos 배열안에 Javascript용으로 변환한 myMemo 넣기 createMemo(); delMemo(); editMemo(); checkMemo(); } } loadMemo();
로컬스토리지에 저장하는 함수를 saveMemo로 따로 만들어줬다. 단지 두줄밖에 되지 않지만 매번 로컬스토리지의 코드들은 익숙하지가 않아서 그냥 내가 보기 편하게 따로 빼줬다.
그리고 로컬스토리지에 저장된 값을 loadMemo getItem으로 받아왔고 이걸 JSON.parse를 사용해 JavaScript에서 사용할 수 있게 변환했고 변환한 값을 전역변수로 만든 memos에 할당했다.
이곳에서 어려웠던 점은 로컬스토리지는 할 때마다 헷갈리는 거 같다.
코드를 치려고 하면 항상 손이 안 가고 "변환하고" "저장하고" 아닌가..? 뭐 이런 식이다. 자꾸 몇 번을 생각하게 만드는 안 친해지는 코드 중 하나다.
메모 삭제하기
function delMemo(){ // 메모 삭제하기 const delBtn = document.querySelectorAll('.del-btn'); // 삭제 버튼 delBtn.forEach((item) => { item.addEventListener('click', () => { const memoId = Number(item.closest('.memo-paper').querySelector('.memo-check').id); // 삭제 버튼에서 체크박스 id값 받아내기 // memos 배열에서 memoId와 일치하는 메모 제거 memos = memos.filter((memo) => { return memo.id !== memoId // 체크박스의 id값과 memos의 id값을 비교 (같은 값 삭제) }); createMemo(); saveMemo(); editMemo(); }); }); }
메모를 삭제하는 곳에서부터는 약간 위기가 왔다.
로직도 조금 길어졌고 다른 이벤트도 생각해야 하고 GPT를 엄청 찾아댔지..
내가 클릭한 삭제버튼의 아이디 값을 찾아야 하는데 여긴 진짜 한참을 헤맸다. GPT에서 얻은 코드로는 해결이 불가능했는데 closest 메서드라는 게 있었다. closest() 메서드는 가장 가까운 조상의 요소를 찾아준다고 한다. 그래서 내 아이디 값을 적용했던 체크박스 id값이 있었고 각각의 체크박스를 제어하기 위해 만들었는데 이걸 사용하면 될 것 같았다.
그리고 체크박스의 id값과 배열에 있는 id값들을 filter로 비교했고 같은 값을 가진 객체를 삭제 후 다시 memos에 할당했다.
퍼블리싱을 하면서 제이쿼리만 사용했던 나에게 현재 제이쿼리를 쓰지 않으려는 나에게 정말 좋은 기능이었다. 자바스크립트는 항상 요소의 요소를 찾으려면 parentNode 같은 긴 코드를 여러 번 작성해서 하나하나 따라갔어야 했는데 이걸 이제 알다니...
그리고 filter는 뭔지 알면서도 사용하려고 하면 늘 어지럽다.
메모 수정하기
function editMemo(){ // 메모 수정하기 const memoList = document.querySelectorAll('.memo-list'); memoList.forEach((item) => { const editTitle = item.querySelector('.edit-title'); // 타이틀 수정 const editBtn = item.querySelector('.edit-btn'); // 삭제 버튼 const saveBtn = item.querySelector('.save-btn'); // 저장 버튼 const editMemo = item.querySelector('.edit-memo'); // 텍스트 수정 editBtn.addEventListener('click', () => { item.classList.add('edit-form'); }); saveBtn.addEventListener('click', () => { item.classList.remove('edit-form'); const memoId = Number(saveBtn.closest('.memo-paper').querySelector('.memo-check').id); // 삭제 버튼에서 체크박스 id값 받아내기 memos.forEach((memo) => { if (memo.id === memoId) { // 클릭한 저장 버튼의 id와 memos에 있는 id값을 비교(같은것 찾기) memo.title = editTitle.value; memo.text = editMemo.value; } }); createMemo(); saveMemo(); }); }); }
처음에 HTML작업할 때 메모 내용에 대한 부분을 수정을 염두에 두고 만들었다.
수정할 메모 공간이 필요했고 수정한 메모를 받아내는 공간이 필요했다.
메모수정 버튼과 수정한 메모를 저장하는 버튼은 각각 교차시키게 만들었고 읽는 메모와 수정메모 영역 또한 교차시켰다.
처음부터 수정하는 메모에 값을 넣었고 수정하는 메모의 값에 따라 읽는 메모가 똑같이 변하도록 만들었다.
읽는메모 수정메모 메모 체크하기
function checkMemo(){ // 체크 상태 const memoList = document.querySelectorAll('.memo-list'); memoList.forEach((item) => { const memoCheck = item.querySelector('.memo-check'); memoCheck.addEventListener('change', () => { const memoId = Number(memoCheck.closest('.memo-paper').querySelector('.memo-check').id); // 삭제 버튼에서 체크박스 id값 받아내기 memos.forEach((memo) => { if (memo.id === memoId) { // 체크한 체크박스의 id와 memos에 있는 id값을 비교(같은것 찾기) if (memoCheck.checked) { memo.checked = 'checked'; } else { memo.checked = ''; } createMemo(); saveMemo(); } }); }); }); }
삭제/수정/체크 함수는 내부에서 약간 다르게 진행될 뿐 방식은 비슷했다.
메모 삭제에서 많은 시간을 허비했지만 삭제를 만들어보니 수정과 체크는 꽤 수월한듯한 느낌이었다.
그렇다고 쉽다는 건 아니다. 어느 정도 시간은 끌었다. 삭제보다 덜 걸렸을 뿐..
체크는 현재 내가 체크한 메모의 상태에 따라 checked의 값이 들어오고 나온다.
이로써 메모장 콘셉의 투두리스트는 마무리되었다.
아쉬운 점이 있다면 읽는 메모를 클릭했을 때 선택한 메모가 팝업처럼 중앙에 오고 사이즈가 두 배 커지는 기능을 만들고 싶었는데 뭐가 꼬인 건지 기능이 한 번만 실행되고 새로고침 하지 않는 이상 실행되지 않았다.
그리고 또 한 가지 페이지가 처음에 로드될 때 각각의 메모지의 색상과 각도가 설정되는데 현재 삭제/수정/체크를 해도 색상과 각도가 바뀐다. 이것도 나쁘지는 않은데 의도와는 다르게 흘러가서 약간 찝찝하다.
해결을 하고 싶었으나 여기에만 시간을 쏟을 수가 없어서 아쉬운 점을 남기고 나중에 다시 도전해 보기로 한다.
완성본
'만든 화면 찌끄리기' 카테고리의 다른 글
React로 간단한 To-Do-List 만들기 (0) 2024.09.24 동물 정보 사이트 만들기 (2) 2024.07.31 회원가입 유효성 검사(feat.todoList) (2) 2024.07.22 롯데월드 클론코딩(react) - 로그인, 회원가입 (2) 2024.07.16 롯데월드 클론코딩(react) (6) 2024.07.15