벌써 이만큼이나 만들었네요!
이렇게 만든거.. 새로고침하면 싹 날아가잖아요 🥺
애정을 담아 만든건데 🥺🥺
그래서 오늘은 로컬 스토리지에 데이터를 저장하고 꺼내 써볼 거예요!
그럼 새로고침해도 데이터가 남아있어서 날아가지 않아요!
와..~!
❗localStorage란?❗
localStorage를 사용하면, 브라우저에 key-value 값을 Storage에 저장할 수 있습니다.
저장한 데이터는 세션간에 공유됩니다.
즉, 세션이 바뀌어도 저장한 데이터가 유지됩니다.
📍 저장해 줄 데이터는 어떤 게 있을까요?
우선 리스트들을 저장해 볼까요!
Script
function createTodo () {
const todoList = document.querySelector('#todoList');
console.log(todoList.children[0].querySelector('span').textContent)
}
textContent
로 사용합니다.function saveItemsFn () { // 로컬에 데이터 저장하기
const saveItems = []; // 빈 배열 할당
for (let i = 0; i < todoList.children.length; i++){
saveItems.push(); // 배열 추가
}
}
saveItemsFn
이라는 이름으로 함수를 생성합니다.반복문
사용push()
메소드 사용function saveItemsFn () { // 로컬에 데이터 저장하기
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)
}
아까 저장해 줄 데이터가 2개 있다고 했죠?
객체 형태로 담아서 저장해 볼게요.
contains
메소드로 complete 라는 클래스명이 있는지 확인할게요.push()
합니다 -> 배열 안에 객체가 포함되는 구조!📝 콘솔 확인
배열 안에 객체가 잘 담겼죠?
complete에는 false
가 나오네요! 🧐
왜냐? contains는 boolean
값으로 반환을 해주기 때문입니다!
complete라는 클래스명이 있으면 true를! 없으면 false를 반환해 줘서
지금은 false만 나오고 있네요
newBtn.addEventListener('click', () => {
newLi.classList.toggle('complete');
saveItemsFn();
});
체크박스 버튼을 누를때 마다 확인을 해줘야 하니까 newBtn 클릭 이벤트에 넣어줍니다.
이제..! 이 배열을 로컬에 저장하겠습니다!
그런데.. 로컬에는 문자열만 저장이 가능한 사실.. 알고 있었나요..? 🫠
산 넘어 산이네요..🏔
배열이나 객체 자체를 문자열로 변환해 줄 수 있는 JSON
이라는 데이터 포맷이 존재!
JSON
은 데이터 통신할 때 많이 사용하고 로컬 스토리지에 배열이나 객체 데이터를 저장할 때도 많이 사용합니다!
function saveItemsFn () { // 로컬에 데이터 저장하기
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(JSON.stringify(saveItems)) // 문자열로 변환
}
JSON
사용방법은 .stringify()
라는 메소드와 같이 쓰입니다.
JSON.stringify()
- () 안에 문자열로 변환하고 싶은 데이터를 넣어주면 됩니다!
📝 콘솔 확인
문자열로 잘 나오네요!
이제 진짜 로컬 스토리지에 저장해 봅시다! 🥹
❗setItem() - localStorage에 저장하기❗
- localStorage.setItem(key, value)
나중에 로컬 스토리지 사용법도 포스팅 할게요!
(점점 쌓여가는 임시글..)
function saveItemsFn () { // 로컬에 데이터 저장하기
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); // 배열 추가
}
localStorage.setItem('saved-items', JSON.stringify(saveItems)); // localStorage 추가
}
key
값으로는 saved-items
이라는 이름을 지어줬고
value
값에는 문자열로 변환된 JSON.stringify(saveItems)
를 넣어줬습니다.
개발자 도구를 열어서 Application
에 들어가보면 localStorage에 잘 저장된걸 확인할 수 있습니다!
와~! 🥳
이제 새로고침하거나 브라우저를 나갔다가 다시 들어왔을때 localStorage에서 데이터를 가져올 수 있게 작업을 해볼게요!
❗getItem() - localStorage에서 가져오기❗
- localStorage.getItem(key)
const savedTodoList = localStorage.getItem('saved-items');
console.log(savedTodoList)
가져오고 싶은 데이터의 key 값을 적습니다.
📝 콘솔 확인
오호 이게 뭘까요?
아까 변환시켜준 문자열
이네요!
아까 객체 형태로 저장해뒀는데 문자열로 나오네? 안되지 안돼 ❌❌❌
다시 원본 데이터 형태로 변환합시다!
이랬다 저랬다 손이 많이 가네..
❗JSON.parse()❗
JSON 문자열을 객체나 배열로 변환하는 메소드
parse 메소드를 사용해 줄게요 🪄
const savedTodoList = JSON.parse(localStorage.getItem('saved-items'));
console.log(savedTodoList)
📝 콘솔 확인
짜잔 다시 원래되로 돌아왔어요!
이제 savedTodoList 를 활용해 볼게요!
const savedTodoList = JSON.parse(localStorage.getItem('saved-items'));
if (savedTodoList) { // savedTodoList(로컬 데이터)가 존재하면 실행
for(let i = 0; i < savedTodoList.length; i++){
createTodo(savedTodoList[i]) // 전달인자로 전달
}
}
function createTodo (storageData) { // 매개변수
const todoList = document.querySelector('#todoList');
const newLi = document.createElement('li');
const newBtn = document.createElement('button');
const newSpan = document.createElement('span');
const todoInput = document.querySelector('#todoInput');
const deleteAll = document.querySelector('.delete-btn-wrap');
newLi.appendChild(newBtn);
newLi.appendChild(newSpan);
// console.log(newLi)
newSpan.textContent = todoInput.value;
todoList.appendChild(newLi);
// console.log(todoList)
todoInput.value = '';
newBtn.addEventListener('click', () => {
newLi.classList.toggle('complete');
saveItemsFn();
});
newLi.addEventListener('dblclick', () => {
newLi.remove();
});
saveItemsFn();
};
❗전달인자와 매개변수❗
전달인자 : 말 그대로 전달하는 인자!
매개변수 : 함수를 호출할 때 인수로 전달된 값을 함수 내부에서 사용할 수 있게 해주는 변수
로컬에 데이터가 존재하면
그 데이터의 갯수만큼 createTodo
를 실행시켜라savedTodoList[i]
를 전달인자로 전달해줍니다.storageData
라는 이름의 매개변수로 받을게요!갑자기 너무 어려워진 느낌.. 😵💫
이제 매개변수인 storageData
를 사용해 볼게요!
const todoInput = document.querySelector('#todoInput');
function createTodo (storageData) {
let todoContents = todoInput.value;
if (storageData) {
todoContents = storageData.contents
}
const todoList = document.querySelector('#todoList');
const newLi = document.createElement('li');
const newBtn = document.createElement('button');
const newSpan = document.createElement('span');
const deleteAll = document.querySelector('.delete-btn-wrap');
newLi.appendChild(newBtn);
newLi.appendChild(newSpan);
// console.log(newLi)
newSpan.textContent = todoInput.value;
todoList.appendChild(newLi);
// console.log(todoList)
todoInput.value = '';
newBtn.addEventListener('click', () => {
newLi.classList.toggle('complete');
saveItemsFn();
});
newLi.addEventListener('dblclick', () => {
newLi.remove();
});
saveItemsFn();
};
todoInput.value
를 할당합니다.todoInput.value는 저기에 입력된 값이죠?! ⤴
storageData
가 존재한다면(데이터가 존재한다면) todoContents
변수에 storageData.contents
를 재할당 해줍니다.storageData.contents 는 저기! 객체가 담겨져 있는 contents!
todoInput의 value와 같은 값이죠!
newSpan.textContent = todoInput.value;
그러면 createTodo 함수 안에 있는 이 코드를
newSpan.textContent = todoContents;
이렇게 바꿀 수 있겠죠!
📝 실행 화면
새로고침 해도 데이터가 날라가지 않습니다! 🥳
완료 표시도 데이터가 불러왔을때 있어야하니까
if (storageData && storageData.complete === true) {
newLi.classList.add('complete')
}
createTodo()
함수 안에 if문 하나 넣어줍니다.
newLi.addEventListener('dblclick', () => {
newLi.remove();
saveItemsFn();
});
더블클릭 했을 때도 저장해줍시다!
function deleteAll() { // 전체 삭제 버튼
const liList = document.querySelectorAll('#todoList li');
// console.log(liList[0])
for ( let i = 0; i < liList.length; i++){
liList[i].remove();
// console.log(liList[i])
}
saveItemsFn();
};
똑같이 전체 삭제 했을때도!
📝 실행 화면
와앙! 🥹🥹
그런데 전체 삭제를 눌렀을 때 Application 을 보면 []
빈배열이 남아있죠?
이거 필요없으니까 없애주겠습니다!
❗removeItem() - localStorage에 값 삭제하기❗
- localStorage.removeItem(key)
function saveItemsFn () {
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);
}
if (saveItems.length === 0) { // 데이터가 없다면 값 삭제
localStorage.removeItem('saved-items')
}else{
localStorage.setItem('saved-items', JSON.stringify(saveItems));
}
}
📝 실행 화면
깔끔 ✨
📝 전체 코드
<!DOCTYPE html>
<html lang="en">
<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 rel="stylesheet" href="./style.css">
<script type="text/javascript" src="./todo.js"></script>
</head>
<body>
<div class="todo-container">
<h1>Todo</h1>
<div id="inputField">
<input type="text" id="todoInput" placeholder="할 일 추가하기" onkeydown="keyCodeCheck()">
<button type="button" id="addBtn">
<span></span>
<span></span>
</button>
</div>
<ul id="todoList">
</ul>
<div class="delete-btn-wrap">
<button onclick="deleteAll();">전체 삭제</button>
</div>
</div>
</body>
</html>
CSS
* {
box-sizing: border-box;
}
body {
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
min-height: 100vh;
}
h1 {
margin: 0;
margin-bottom: 12px;
}
.todo-container {
max-width: 400px;
width: 100%;
background-color: #FFE8AD;
text-align: center;
padding: 20px;
margin-top: 40px;
}
#inputField #todoInput {
width: calc(100% - 45px);
border: 1px solid #eee;
border-radius: 4px;
padding: 10px;
}
#inputField #todoInput:focus {
outline: none;
}
#inputField #addBtn {
position: relative;
width: 35px;
height: 35px;
border: none;
background-color: #242423;
border-radius: 4px;
cursor: pointer;
vertical-align: middle;
}
#inputField #addBtn span {
display: block;
width: 2px;
height: 15px;
background-color: #FFE8AD;
position: absolute;
transform: translate(-50%,-50%);
top: 50%;
left: 50%;
}
#inputField #addBtn span:last-child {
transform: translate(-50%,-50%) rotate(-90deg);
}
#todoList {
list-style: none;
margin: 0;
padding: 10px;
text-align: left;
}
#todoList li {
padding: 10px 0;
user-select: none;
}
#todoList li.complete {
text-decoration: line-through;
color: rgb(155, 155, 155);
}
#todoList button {
width: 15px;
height: 15px;
background-color: #fff;
margin-right: 8px;
border: none;
border-radius: 4px;
cursor: pointer;
}
#todoList li.complete button::after {
content: "";
display: block;
width: 5px;
height: 10px;
transform: translate(-2px, -2px) rotate(45deg);
border-right: 2px solid #f1b116;
border-bottom: 2px solid #f1b116;
}
.delete-btn-wrap button {
cursor: pointer;
border: none;
border-radius: 8px;
background-color: #242423;
color: #fff;
padding: 8px;
}
Script
const todoInput = document.querySelector('#todoInput');
const addBtn = document.querySelector('#addBtn');
const savedTodoList = JSON.parse(localStorage.getItem('saved-items'));
console.log(savedTodoList)
if (savedTodoList) { // 로컬에서 데이터 가져오기
for(let i = 0; i < savedTodoList.length; i++){
createTodo(savedTodoList[i])
}
}
function keyCodeCheck () { // 엔터키로 추가
if(window.event.keyCode === 13 && todoInput.value !== ''){
createTodo();
}
}
addBtn.addEventListener('click', () => { // + 버튼으로 추가
if(todoInput.value !== ''){
createTodo();
}
});
function createTodo (storageData) { // 할 일 추가 기능
let todoContents = todoInput.value;
if (storageData) {
todoContents = storageData.contents
}
const todoList = document.querySelector('#todoList');
const newLi = document.createElement('li');
const newBtn = document.createElement('button');
const newSpan = document.createElement('span');
const deleteAll = document.querySelector('.delete-btn-wrap');
newLi.appendChild(newBtn);
newLi.appendChild(newSpan);
newSpan.textContent = todoContents
todoList.appendChild(newLi);
todoInput.value = '';
newBtn.addEventListener('click', () => { // 체크박스 클릭
newLi.classList.toggle('complete');
saveItemsFn();
});
newLi.addEventListener('dblclick', () => { // 더블 클릭
newLi.remove();
saveItemsFn();
});
if (storageData && storageData.complete === true) {
newLi.classList.add('complete')
}
saveItemsFn();
};
function deleteAll() { // 전체 삭제 버튼
const liList = document.querySelectorAll('#todoList li');
for ( let i = 0; i < liList.length; i++){
liList[i].remove();
}
saveItemsFn();
};
function saveItemsFn () { // 로컬에 데이터 저장하기
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);
}
if (saveItems.length === 0) {
localStorage.removeItem('saved-items')
}else{
localStorage.setItem('saved-items', JSON.stringify(saveItems));
}
}
와아..!
내가 헷갈려서 하는 정리 💦
if (savedTodoList) { // 로컬에서 데이터 가져오기
for(let i = 0; i < savedTodoList.length; i++){
createTodo(savedTodoList[i])
}
}
createTodo
함수에 담겨짐 -> storageData
라는 매개변수로 받음function createTodo (storageData) { // 할 일 추가 기능
let todoContents = todoInput.value;
if (storageData) {
todoContents = storageData.contents
}
const todoList = document.querySelector('#todoList');
const newLi = document.createElement('li');
const newBtn = document.createElement('button');
const newSpan = document.createElement('span');
const deleteAll = document.querySelector('.delete-btn-wrap');
newLi.appendChild(newBtn);
newLi.appendChild(newSpan);
newSpan.textContent = todoContents
todoList.appendChild(newLi);
todoInput.value = '';
newBtn.addEventListener('click', () => { // 체크박스 클릭
newLi.classList.toggle('complete');
saveItemsFn();
});
newLi.addEventListener('dblclick', () => { // 더블 클릭
newLi.remove();
saveItemsFn();
});
if (storageData && storageData.complete === true) {
newLi.classList.add('complete')
}
saveItemsFn();
};
✨ 결과물
와아 드디어 끝났습니다..! 🥹
로컬에서 가져오는 부분에서 헤맸는데..
좀 더 연습하고 실습을 많이 해봐야 할 거 같아요
멀고도 먼 자바스크립트의 길..
앞으로도 계속 해멜 거 같지만 그래도 열심히 걸어가 보겠습니다..! 👊