식료품 리스트 만들기
이번 튜토리얼 포스트 부터는 양식을 좀 바꿔보려고 한다.
38가지 자바스크립트 & 리액트 튜토리얼의 14번, 식료품 리스트를 만들어보자.
간단한 CRUD를 경험해볼 수 있다.
작업 개요
1. 상단의 input 영역에 식료품명을 입력하면 하단에 리스트 형식으로 추가 되어야함.
2. 추가된 리스트는 수정 및 삭제가 가능해야 함.
3. 하단의 Clear items로 모든 리스트를 삭제 할 수 있어야함.
HTML
우선 스크립트를 작성하기 전에 HTML을 작성해보자.
<div class="food_list_container">
<h1>식료품 리스트</h1>
<form>
<input type="text" placeholder="계란" />
<buttom type="submit">등록</button>
</form>
<ul>
<li>
<p>임시</p>
<button type="button">수정</button>
<button type="button">삭제</button>
</li>
</ul>
</div>
제목과, 입력을 받을 수 있는 form, 그리고 등록된 리스트가 나올 ul로 구성했다.
여기서, 수정을 누르면 리스트 아이템의 p 부분이 input으로 변경되야 하므로 임시로 하나 더 추가해보면..
<div class="food_list_container">
<h1>식료품 리스트</h1>
<form>
<input type="text" placeholder="계란" />
<buttom type="submit">등록</button>
</form>
<ul>
<li> // 수정
<input type="text" value="임시" />
<button type="button">수정</button>
<button type="button">삭제</button>
</li>
<li>
<p>임시</p>
<button type="button">수정</button>
<button type="button">삭제</button>
</li>
</ul>
</div>
저런식으로 나오게 된다고 생각해보자.
여기서 등록 부분은 form이고, 수정 부분은 form을 사용하지 않았는데.
주로 form을 쓰는 경우에는 form method를 이용해 get 또는 post로 서버와의 통신을 위해 주로 사용되니까
지금은 따로 서버와 통신을 하진 않을꺼니까 form을 div로, button submit을 일반 button으로 바꿔주자.
<div class="food_list_container">
<h1>식료품 리스트</h1>
<div class="input_row">
<input type="text" placeholder="계란" />
<buttom type="button">등록</button>
</div>
<ul>
<li> // 수정
<input type="text" value="임시" />
<button type="button">수정</button>
<button type="button">삭제</button>
</li>
<li>
<p>임시</p>
<button type="button">수정</button>
<button type="button">삭제</button>
</li>
</ul>
</div>
기본 티스토리 블로그 css때문에 뭐가 뭔지 모르겠어서,
모양을 좀 다듬어 주기 위해 간단한 css를 적용했다.
식료품 리스트
-
임시
이제 스크립트 작업을 해보자.
JS
만들어야 할 기능들을 리스트로 먼저 작성해보면,
1. 입력된 식료품 텍스트를 받아 리스트에 추가. (C)
2. 수정을 누르면 등록된 아이템의 인풋이 바뀌고, 한번더 수정을 누르면 바뀐 내용이 등록. (U)
3. 삭제를 누르면 리스트에서 아이템이 제거. (D)
1. 등록
등록을 위해 .input_row div 내부의 input과 button을 변수로 할당 후,
"등록" 버튼 클릭 시 input에 입력된 값이 리스트에 추가되고,
그와 동시에 input에 입력되있던 내용을 지워주는 함수를 만들어보자.
const inputRow = document.querySelector('.input_row');
const inputBtn = inputRow.querySelector('button');
const inputText = inputRow.querySelector('input[type="text"]');
const listContainerUl = document.querySelector('.food_list_container ul');
const handleCreateList = () => {
listContainerUl.appendChild(createLi(inputText.value));
inputText.value = '';
}
const createLi = (innerText) => {
const li = document.createElement('li');
const p = document.createElement('p');
const input = document.createElement('input');
const editBtn = document.createElement('button');
const deleteBtn = document.createElement('button');
p.innerText = innerText;
input.type = 'text';
input.value = innerText;
editBtn.innerText = '수정';
deleteBtn.innerText = '삭제';
li.appendChild(p);
li.appendChild(editBtn);
li.appendChild(deleteBtn);
return li;
}
inputBtn.addEventListener('click', handleCreateList);
우선 각각의 dom을 변수로 할당했고,
등록 버튼이 클릭 됬을 때 handleCreateList 함수를 작동해 입력된 내용을 ul 내부에 li로 만들어서 넣어주었다.
여기까지 하면, 입력하고 등록해서 리스트에 추가가 가능해진다.
식료품 리스트
여기서 조금만 더 손을 보면,
handleCreateList 함수 앞부분에 inputText.value가 '' 일 경우 return 시켜서
빈 리스트가 안들어가게 하면 더 좋을것 같다.
2. 삭제
CRUD긴 한데, 쉬운거부터 해보자.
삭제는 그냥 클릭된 리스트를 제거해주면 되니까 한번 해보자.
handleDelete 함수를 새로 만들고, 기존 createLi 함수를 조금 수정할건데,
const inputRow = document.querySelector('.food_list_container .input_row');
const inputBtn = inputRow.querySelector('button');
const inputText = inputRow.querySelector('input[type="text"]');
const listContainerUl = document.querySelector('.food_list_container ul');
const handleCreateList = () => {
if(inputText.value === '') return;
listContainerUl.appendChild(createLi(inputText.value));
inputText.value = '';
}
const handleDelete = (e) => {
const li = e.target.parentNode;
listContainerUl.removeChild(li);
}
const createLi = (innerText) => {
const li = document.createElement('li');
const p = document.createElement('p');
const input = document.createElement('input');
const editBtn = document.createElement('button');
const deleteBtn = document.createElement('button');
p.innerText = innerText;
input.type = 'text';
input.value = innerText;
editBtn.innerText = '수정';
deleteBtn.innerText = '삭제';
deleteBtn.addEventListener('click', handleDelete);
li.appendChild(p);
li.appendChild(editBtn);
li.appendChild(deleteBtn);
return li;
}
inputBtn.addEventListener('click', handleCreateList);
handleDelete는 자신의 부모 노드(li)를 listContainerUl에서 removeChild 시키는 역할을 하고,
createLi에서 리스트 아이템이 생성될 때, deleteBtn에 이벤트 리스너로 handleDelete를 등록해주었다.
이제 삭제도 동작한다.
식료품 리스트
3. 수정
수정은 조금 까다롭다. 일단 input 태그가 노출이 되어야 하고,
수정란에 입력된 값이 반영 되어야 하며,
수정중 취소를 누를 경우 다시 원본으로 복구되어야 한다.
삭제와 마찬가지로, createLi 함수 내에서 edit 버튼에 이벤트를 등록하기 위해 handleEdit 함수를 만들어보자
const inputRow = document.querySelector('.food_list_container .input_row');
const inputBtn = inputRow.querySelector('button');
const inputText = inputRow.querySelector('input[type="text"]');
const listContainerUl = document.querySelector('.food_list_container ul');
const handleCreateList = () => {
if(inputText.value === '') return;
listContainerUl.appendChild(createLi(inputText.value));
inputText.value = '';
}
const handleDelete = (e) => {
const li = e.target.parentNode;
listContainerUl.removeChild(li);
}
const handelEdit = (e) => {
const li = e.target.parentNode;
const input = li.querySelector('input[type="text"]');
const deleteBtn = li.querySelector('button:last-child');
const p = li.querySelector('p');
if(input.style.display === 'none') {
input.style.display = 'block';
p.style.display = 'none';
input.focus();
e.target.innerText = '완료';
deleteBtn.innerText = '취소';
const handleCancel = () => {
input.style.display = 'none';
input.value = p.innerText;
p.style.display = 'block';
e.target.innerText = '수정';
deleteBtn.innerText = '삭제';
deleteBtn.removeEventListener('click', handleCancel);
deleteBtn.addEventListener('click', handleDelete);
}
deleteBtn.removeEventListener('click', handleDelete);
deleteBtn.addEventListener('click', handleCancel);
} else {
input.style.display = 'none';
p.style.display = 'block';
e.target.innerText = '수정';
p.innerText = input.value;
deleteBtn.addEventListener('click', handleDelete);
deleteBtn.innerText = '삭제';
}
}
const createLi = (innerText) => {
const li = document.createElement('li');
const p = document.createElement('p');
const input = document.createElement('input');
const editBtn = document.createElement('button');
const deleteBtn = document.createElement('button');
p.innerText = innerText;
input.type = 'text';
input.value = innerText;
input.style.display = 'none';
editBtn.innerText = '수정';
deleteBtn.innerText = '삭제';
deleteBtn.addEventListener('click', handleDelete);
editBtn.addEventListener('click', handelEdit);
li.appendChild(p);
li.appendChild(input);
li.appendChild(editBtn);
li.appendChild(deleteBtn);
return li;
}
inputBtn.addEventListener('click', handleCreateList);
createLi 내부가 조금 변경되었는데, 우선 기존에 p만 append 시키던 부분을
p와 input을 둘다 append를 해주고, 초기 style을 display: none 속성을 주었다.
마찬가지로 editBtn에 이벤트 리스너를 달아주었다.
handleEdit 함수는 handleDelete와 마찬가지로 부모 노드(li)와 내부의 p, input 그리고 deleteBtn을 변수로 할당한다.
(editBtn은 e.target으로 이용)
"수정" 버튼이 눌렸을 때 p대신 input이 노출되고, p는 숨겨지고, 수정, 삭제 버튼이 완료, 취소 버튼으로 변경되야 하므로
해당 처리를 해준다. 이후 handleCancel 함수를 만들어 주었는데, "삭제"와 "취소" 버튼이 각각 동작해야 하는
방식이 달라서 이벤트리스너를 익명으로 등록 시 제거가 안될것 같아 추가했다.
input의 style - display가 none일 경우 (초기상태)
수정버튼을 완료로, 그리고 삭제버튼을 취소버튼으로 만들어주는데,
이때 삭제버튼의 텍스트를 바꾸면서 삭제이벤트를 제거해주고, 취소 이벤트를 할당해준다.
취소 이벤트가 발생하면 다시 텍스트를 바꿔주고 handleDelete를 삭제 버튼에 붙여준다.
이후, input의 style - display가 none일 경우(완료 버튼 클릭 시)
p의 내용을 바꿔주고 버튼도 원래대로 돌려준다.
이러면 등록, 수정, 삭제가 가능해지므로 CRUD 작업이 끝났다.
식료품 리스트