본문 바로가기

JS - todoList 본문

개발/JavaScript

JS - todoList

자전하는명왕성 2022. 12. 29. 20:13

잠깐 HTML, ul태그와 ol태그의 차이

 

<ul>
	<li>ul</li>
	<li>Unordered List</li>
</ul>
<ol>
	<li>ol</li>
	<li>Order List</li>
</ol>

종속 태그로는 둘다 li 태그 사용

 

DOM(document object model)

브라우저가 HTML 문서를 파싱하는 과정에서 생겨나는 객체

아래처럼 나무와 비슷하게 생긴 모양 때문에 트리 구조라고 함

그리고 각각의 나뭇잎처럼 생긴 요소들은 노드라고 함

그리고 이 노드들은 객체 형태를 띔

 

onkeydown()

키보드가 눌릴 때마다 실행시켜줌

<input
	type="text"
	id="todo-input"
	onkeydown="keyCodeCheck()"
/>
            
--
const keyCodeCheck = function () {
    console.log(window.event.keyCode === 13)
    if(window.event.keyCode === 13){
        const inputValue = document.querySelector("#todo-input").value
        console.log(inputValue)
    }
}

// '엔터'의 keyCode === 13

 

createElement 로 노드 생성 및 appendChild로 하위 속성 추가

	// createElement = 노드 생성 
        const newLi = document.createElement('li')
        const newSpan = document.createElement('span')

	// appendChild = 하위 속성으로 추가
        newSpan.textContent = todoInput.value;
        newLi.appendChild(newSpan)
        todoList.appendChild(newLi)

노드 생성은 상위 요소에서 하위 요소 순으로,

하위 속성 추가는 하위 요소에서 상위 요소 순으로 해야 헷갈리지 않는다

완성한 뒤, input내에 값을 입력하고 엔터를 누르면, 작성한 값이 아래에 추가됨을 확인할 수 있음

 

이제 '할 일'과 '한 일'을 구분하기 위한 작업이 필요

// 버튼 노드 추가
    const newBtn = document.createElement('button')

// 버튼에 이벤트 리스너 추가
// '버튼' '클릭' 시, 'newLi'에 class="coplete" 를 부여하겠다는 의미
    newBtn.addEventListener('click', ()=> {
        newLi.classList.toggle('complete')
    })
    
// 그리고 만들어진 버튼을 'newLi'에 요소로 추가
    newLi.appendChild(newBtn)

 

이후 '버튼'과 'complete'에 css 요소를 추가하면

아래와 같은 화면을 만들 수 있게 됨

 

리스트 삭제하기

 

// HTML 내 버튼 만들기
        <div class="delete-btn-wrapper">
            <button onclick="deleteAll()">전체 삭제</button>
        </div>
        
--
// js 내 삭제 펑션
	const deleteAll = function () {
    	const liList = document.querySelectorAll('li')
	    for(let i = 0; i<liList.length ; i++){
    	    liList[i].remove()
		}
	}

HTML 버튼에 클릭 시 deleteAll() 실행하도록 함

deleteAll()은, 'li'를 가진 모든 요소를 불러다가 삭제

 

스토리지에 저장

먼저, 저장하기 위한 function을 만듬

const saveItemsFn = function () {
    const saveItems = [];
    for(let i = 0 ; i < todoList.children.length; i++) {
        const todoObj = {
            contents : todoList.children[i].querySelector('span').textContent,
            complete : todoList.children[i].classList.contains('complete')
        }
        saveItems.push(todoObj)
    }
    console.log(saveItems, 'saveItems')
}

저장할 요소들을 todoObj에 객체화하여, 저장하게 되는데 이 때,

contents : 각 span 이 지니고 있는 textContent

complete : 는 각각의 li가 class="complete"를 가지고 있는 여부(contains)를 판단하여 true / false로 반환해줌

그리고, 객체 todoObj 를 배열 saveItem 안에 넣어주는 과정으로 해당 데이터를 저장

 

-- 잠깐 살펴봐야 할 개념

호이스팅

'함수 선언문'에서는 호이스팅이 발생하는데, 함수 발생 이전에 해당 함수가 스크립트 맨 위로 끌어올려진 것 같은 효과를 냄

그말인즉슨, 이는 스크립트 내에서 예상하지 못하는 동작을 할 가능성이 높기 때문에 사용을 지양하는 것이 좋음

추가적으로 var 역시 호이스팅이 발생함

스코프

 

  • 전역 스코프 : 스크립트 전역에 선언된 변수 / 어디서든 참조 가능
  • 지역 스코프 : 함수 내에 선언된 변수 / 함수 내에서만 참조 가능

let / const , var 사이의 가장 큰 차이점 중 하나는 스코프와 관련이 있음

let / const 는 '블록 스코프, 함수 스코프'라는 특징을 가지고 있는데,

블록 스코프는, 함수 안에서 선언된 let / const 의 경우 함수 안에서만 처리될 수 있다는 의미를 지니고 있음

반대로 var는 '함수 스코프'라는 특징만을 가지고 있기 때문에 호이스팅이 발생할 수 있는 것임

따라서, 예측 가능한 코드 작성을 위해서는 let / const 를 통해 변수를 선언하거나 함수 선언문을 통해 선언하는 것이 더 적절함

 

다시, 로컬 스토리지

로컬 스토리지에는 문자열로 저장해야 함

이때 배열은 String 함수를 통해 문자열화 시켜줄 수 없음

이러면 JSON이라는 데이터 포맷을 활용해줘야함

JSON.stringify(saveItems)

 

'JSON' 포멧을 활용하여, '문자열화' 해줌 이라는 의미

이를 콘솔값 JSON.stringify(saveItems) 으로 뽑아내면 아래와 같은 값을 얻을 수 있음

 

원하는 값을 얻었으니 로컬 스토리지에 저장하면 됨

localStorage.setItem('saved-items', JSON.stringify(saveItems))

그러면, 위와 같은 형태로 객체가 로컬 스토리지에 저장됨을 확인할 수 있음

이후 페이지를 열 때마다 저장된 요소를 꺼내오기 위해 최상단에 저장된 객체를 불러옴

const savedTodoList = JSON.parse(localStorage.getItem("saved-items"))

// 이때 JSON.parse 는 위에 저장하기 위해 stringify해준 것을 원래대로 돌려놓기 위함

그리고 가져온 데이터를 다시 구현하기 위해 최하단에 위와 같이 작성

// createTodo 호이스팅 때문에 맨 아래로 내려주었음
if(savedTodoList){
    for(let i=0; i<savedTodoList.length; i++) {
        createTodo(savedTodoList[i])
    }
}

이후 저장된 데이터가 있다면, 저장된 컨텐츠를 todoContents에

입력된 값이 있다면, todoContents로 할당

어쨌든 todoContents라는 변수로 처리되고 있기 때문에 상관없음

const createTodo = function (storageData) {
    let todoContents = todoInput.value
    if(storageData){
        todoContents = storageData.contents
    }

추가로 받아온 객체의 complete 값이 true 라면,

기존에 만든 complete 클래스를 추가해줌

// 옵셔널 체이닝
    if(storageData?.complete) {
        newLi.classList.add('complete')
    }

이때 storageData?.complete 는 두가지를 묻는 것임

  • storageData 가 truly 인가?
  • storageData 가 truly라면, stroageData.complete가 true인가?

위와 같은 표현 방식을 옵셔널 체이닝이라고 함

 

마지막 저장된 데이터가 없을 경우 모두 삭제 / 그렇지 않다면 save-item 저장

    saveItems.length === 0 
    ? localStorage.removeItem('saved-items') 
    : localStorage.setItem('saved-items', JSON.stringify(saveItems))
Comments