1. 배경
항목을 추가할 수 있는 To do 리스트를 만들고, 입력받은 데이터를 로컬 스토리지에 저장하여 창을 새로고친 후에도 항목과 체크 상태가 유지되도록 설정하려고 한다.
2. HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LocalStorage</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"><g><path d="M495.9,425.3H16.1c-5.2,0-10.1,2.9-12.5,7.6c-2.4,4.7-2.1,10.3,0.9,14.6l39,56.4c2.6,3.8,7,6.1,11.6,6.1h401.7 c4.6,0,9-2.3,11.6-6.1l39-56.4c3-4.3,3.3-9.9,0.9-14.6C506,428.2,501.1,425.3,495.9,425.3z M449.4,481.8H62.6L43,453.6H469 L449.4,481.8z"/><path d="M158.3,122c7.8,0,14.1-6.3,14.1-14.1V43.4c0-7.8-6.3-14.1-14.1-14.1c-7.8,0-14.1,6.3-14.1,14.1v64.5 C144.2,115.7,150.5,122,158.3,122z"/><path d="M245.1,94.7c7.8,0,14.1-6.3,14.1-14.1V16.1c0-7.8-6.3-14.1-14.1-14.1C237.3,2,231,8.3,231,16.1v64.5 C231,88.4,237.3,94.7,245.1,94.7z"/><path d="M331.9,122c7.8,0,14.1-6.3,14.1-14.1V43.4c0-7.8-6.3-14.1-14.1-14.1s-14.1,6.3-14.1,14.1v64.5 C317.8,115.7,324.1,122,331.9,122z"/><path d="M9.6,385.2c5.3,2.8,11.8,1.9,16.2-2.2l50.6-47.7c56.7,46.5,126.6,71.9,198.3,71.9c0,0,0,0,0,0 c87.5,0,169.7-36.6,231.4-103.2c5-5.4,5-13.8,0-19.2c-61.8-66.5-144-103.2-231.4-103.2c-72,0-142.2,25.6-199,72.5l-50-47.1 c-4.4-4.1-10.9-5-16.2-2.2c-5.3,2.8-8.3,8.7-7.4,14.6l11.6,75L2.2,370.6C1.3,376.5,4.2,382.4,9.6,385.2z M380.9,230.8 c34.9,14.3,67.2,35.7,95.3,63.6c-10.1,10-20.8,19.2-31.9,27.5c-22.4-3.3-29.6-8.8-30.7-9.7c-4-5.7-11.8-7.7-18.1-4.4 c-6.9,3.6-9.5,12.2-5.9,19.1c1.9,3.5,7.3,10.3,22.4,16c-10.1,5.7-20.5,10.7-31.1,15.1C352.4,320.2,352.4,268.6,380.9,230.8z M36.3,255.6l29.4,27.7c5.3,5,13.6,5.1,19.1,0.3c53.2-47.6,120.7-73.7,190-73.7c26.9,0,53.2,3.9,78.5,11.3 c-29.3,44.6-29.3,102,0,146.6c-25.3,7.4-51.6,11.3-78.5,11.3c-69,0-136.3-26-189.4-73.2c-2.7-2.4-13.4-6.3-19.1,0.3l-30.1,28.3 l5.7-40C42.2,293,36.3,255.6,36.3,255.6z"/><circle cx="398.8" cy="273.8" r="14.1"/></g></svg>
<div class="wrapper">
<h2>LOCAL TAPAS</h2>
<p></p>
<ul class="plates">
<li>Loading Tapas...</li>
</ul>
<form class="add-items">
<input type="text" name="item" placeholder="Item Name" required>
<input type="submit" value="+ Add Item">
</form>
</div>
</body>
</html>
form 태그는, 정보를 제출하기 위한 대화형 컨트롤을 포함하는 문서 구획이다.
3. CSS
html {
box-sizing: border-box;
background: url("oh-la-la.jpeg") center no-repeat;
background-size: cover;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
font-family: Futura, "Trebuchet MS", Arial, sans-serif;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
svg {
fill: white;
background: rgba(0, 0, 0, 0.1);
padding: 20px;
border-radius: 50%;
width: 200px;
margin-bottom: 50px;
}
.wrapper {
padding: 20px;
max-width: 350px;
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
margin: 0;
font-weight: 200;
}
.plates {
margin: 0;
padding: 0;
text-align: left;
list-style: none;
}
.plates li {
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
padding: 10px 0;
font-weight: 100;
display: flex;
}
.plates label {
flex: 1;
cursor: pointer;
}
.plates input {
display: none;
}
.plates input + label:before {
content: "⬜️";
margin-right: 10px;
}
.plates input:checked + label:before {
content: "🌮";
}
.add-items {
margin-top: 20px;
}
.add-items input {
padding: 10px;
outline: 0;
border: 1px solid rgba(0, 0, 0, 0.1);
}
4. Javascript
const addItems = document.querySelector('.add-items');
const itemsList = document.querySelector('.plates');
const items = JSON.parse(localStorage.getItem('items')) || [];
먼저, 위와 같이 addItems, itemsList, items 변수에 각각 입력 값을 제출 받을 add-items 클래스의 form 태그, 입력된 값을 표시할 plates 클래스의 ul 태그, 로컬 스토리지의 key 값이 items인 항목들의 value들, 혹은 빈 배열을 할당했다. localStorage.getItem('keyname')은 로컬 스토리지에 저장된 값 중 해당 키에 대한 value를 반환한다.
function addItem(e) {
e.preventDefault();
const text = (this.querySelector('[name=item]')).value;
const item = {
text,
done: false
};
items.push(item);
popluateList(items, itemsList);
localStorage.setItem('items', JSON.stringify(items));
this.reset();
}
addItems.addEventListener('submit', addItem);
앞서 정의한 변수 addItems에, 값이 제출되면 addItem 함수를 실행하는 이벤트 리스너를 설정했다. submit 이벤트가 발생할 때에는 창이 새로고침되면서 실행되므로, 전달되는 이벤트에 preventDefault를 걸어 중단시킬 필요가 있다. 이를 통해 addItem 함수에 자식 요소를 포함한 form 태그가 전달되는데, 이 중 name 속성이 item인 항목은 사용자가 값을 입력하는 text input 태그이다. 그 값을 text 변수에 할당하고, item 변수를 만들어 text: text, done: false 값을 가지는 객체를 할당한다. 즉, item은 사용자가 입력한 값과 done 정보를 포함한 객체가 되고, push를 통해 이를 items에 추가한다. items는 위에서 설정했듯이, 빈 배열이거나, 로컬 스토리지에 저장된 items 키에 대한 value 값들을 불러온 배열이다. 그리고 이벤트를 통해 전달된 form 태그는 reset을 통해 값을 초기화할 수 있다. 이를 통해 사용자가 입력한 값이 제출되면, 다시 입력 창을 초기화한다. 또, setItem(keyName, value)를 통해 이 값들을 localStroage에 items라는 keyName과 value를 저장하며, 입력된 값을 HTML의 리스트로 만들어 화면에 표시하는 populateList 함수를 실행하도록 한다.
※ ES6부터는 text: text처럼, 객체의 요소 중 key와 value가 같은 요소는 한 번만 입력해도 된다.
※ 대괄호[]는 CSS의 속성 선택자이다. 여러 개의 속성이 있는 경우 이를 통해 속성을 선택할 수 있다.
function popluateList(plates = [], platesList) {
platesList.innerHTML = plates.map((plate, i) => {
return `
<li>
<input type="checkbox" data-index=${i} id="item${i}" ${plate.done ? 'checked' : ''} />
<label for="item${i}">${plate.text}</label>
</li>
`;
}).join('');
}
popluateList(items, itemsList);
populateList는 addItem 함수에서 items와 itemsList를 전달받는데, 이는 각각 사용자가 입력한 값이 추가된 배열과 plates 클래스를 가진 ul 태그이다. 이 ul 태그, platesList에 innerHTML을 통해 새롭게 li 태그를 작성하는데, 이를 위해plates 배열에 map API를 이용해 HTML의 문법으로 변경해 준다. li 태그 안에, checkbox와 label을 만들고, checkbox에는 data-index 속성과, id, checked 속성을 부여하는데, checked 속성은 plate.done의 값이 true인 경우에만 checked가 되도록 하였다. label의 for 속성은 input 태그와 label을 연결해 준다. checkbox에서 정한 id에 따라 label과 연결해 주는 것이다. 최종적으로, .join('')을 통해 배열의 모든 요소를 문자열로 바꿔 주면 된다.
사용자로부터 어떠한 이벤트도 전달되지 않아 addItem이나 toggleDone이 실행되지 않을 때, 이를테면 새로고침을 통해 페이지에 접속했을 때에도 로컬 스토리지에 저장된 리스트의들이 표시되어야 한다. 이를 위해 populateList는 항상 실행되는 상태를 유지하도록 외부에 실행 명령을 내려 두어야 한다.
※ map API는 두 번째 인자로 index 값을 가진다.
function toggleDone(e) {
if (!e.target.matches('input')) return;
const el = e.target;
const index = el.dataset.index;
items[index].done = !items[index].done;
localStorage.setItem('items', JSON.stringify(items));
popluateList(items, itemsList);
}
itemsList.addEventListener('click', toggleDone);
itemsList가 클릭되면 체크박스를 체크하고 체크를 푸는 toggleDone 함수를 시행하도록 했다. toggleDone은 event target이 input이 아닌 경우에는 아무것도 반환하지 않도록 했다. 참고로, 여기서 e는 이벤트 자체, 즉 클릭이라는 마우스 이벤트이고, e.target은 이벤트의 대상이 되는 것이므로 체크박스 input이며, this는 호출 대상이므로 ul 태그가 된다. 체크박스 input을 el이라 하고, 이것의 index dataset 속성을 index라고 하면, populateList에서 설정한대로 i값이 index로 반환된다. items 배열에서 이 index 값으로 이에 해당하는 li 태그에 접근하고, 그 done 값을 반대로, 즉 true는 false로, false는 true로 바꿔 준다. 이를 통해 체크박스의 속성 checked가 생기거나 사라진다. 이렇게 수정된 items 배열을 localStorage에 저장하기 위해 setItem 매소드를 이용하고, 변경된 값들을 다시 표시하기 위해 내부에서 populateList를 실행시킨다.
[Javascript] 16. Mouse Move Shadow (0) | 2021.04.28 |
---|---|
[Javascript] 01~05 Review (0) | 2021.04.27 |
[Javascript] 14. JavaScript References VS Copying (0) | 2021.04.25 |
[Javascript] 13. Slide in on Scroll (0) | 2021.04.24 |
[Javascript] 12. Key Sequence Detection (0) | 2021.04.22 |
댓글 영역