2023. 1. 6. 18:55ㆍ기록/Projects
- 목차
배포 링크 : todo-array
원본 저장소 : 깃허브
이번에 진행한 프로젝트는 간단한 할 일 관리 프로젝트이다!
어떻게 디자인을 하면 좋을 지 구상하다가,
아이패드에서 어플을 실행하는 것 처럼 보여지면 재미있을 것 같았다.
그래서 아이패드, 애플펜슬 이미지를 사용했다!
실제로 내 아이패드는 그저 강의용.....ㅎㅎㅎㅋ
처음에는 자바스크립트로 완성했었는데,
이번에 배운 타입스크립트를 프로젝트에 직접 적용해보고 싶어서
나중에 타입스크립트를 사용해 리팩토링 하게 되었다!
기능 별로 4개의 파일로 나누어 모듈화 하였다.
- main.ts : 페이지 접속 시 맨 처음 실행되어야 하는 로직들
- renderTodo.ts : 각 todo를 렌더링
- requests.ts : API 호출
- setElements.ts : 엘리먼트 조작/설정
할 일 추가
할 일을 입력하고 엔터키를 누르거나 플러스 아이콘을 클릭하면
insert 하는 API를 호출한 후, 데이터 추가가 완료된 목록을 새로 불러와 보여준다.
const addBtnEl = document.querySelector('.add-btn') as HTMLButtonElement;
addBtnEl.addEventListener('click', async () => {
const title = getElementValue('.add-input').trim();
if (title.length === 0) {
alert('할 일을 입력해 주세요.');
return;
}
showElement('.loading');
await insertTodo(title, curOrder);
renderTodoList();
hideElement('.loading');
setElementValue('.add-input');
showToast('추가가 완료되었습니다.');
});
addInputEl.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.isComposing) {
addBtnEl.click();
}
});
할 일 수정 & 체크
할 일을 체크하거나 내용을 수정하면
update 하는 API를 호출하고, 데이터 수정이 완료된 목록을 새로 불러와 보여준다.
수정 후에 수정 일시를 바로 확인할 수 있다.
inputEl.addEventListener('change', () => {
const id = data.id;
const title = inputEl.value;
const done = checkboxEl.checked;
const order = todoItemEl.dataset.order as string;
fnUpdateTodo({ id, title, done, order });
});
순서 변경
Sortable.js 라이브러리를 이용해 드래그&드롭 시 순서를 변경할 수 있도록 구현하였다!
보여지는 순서만 변경되는 것이 아니라, 드롭 시 해당 할 일 데이터의 순서가 실제로 변경된다.
(update 하는 API 호출)
const todosEl = document.querySelector('.todos') as HTMLElement;
const sortable = Sortable.create(todosEl, {
group: 'todos',
animation: 100,
handle: '.order-handle',
onStart: function (event) {
...
},
onEnd: async function () {
if (sortFlag) {
showElement('.loading');
try {
await fnReorderTodo();
renderTodoList();
showToast('순서 변경이 완료되었습니다.');
} catch (error) {
showToast();
} finally {
hideElement('.loading');
}
}
},
});
export async function fnReorderTodo() {
const ids: string[] = [];
const todoItems = document.querySelectorAll('.todo-item') as NodeListOf<HTMLElement>;
todoItems.forEach((item) => {
if (item.dataset.id) ids.unshift(item.dataset.id);
});
await reorderTodo(ids);
}
출력 옵션 설정 (완료 여부, 정렬 순서)
모든 항목 / 완료 항목 / 미완료 항목별로 볼 수 있고
사용자 지정 순 / 최근 순 / 오래된 순으로 볼 수 있다.
할 일 목록을 가져오는 API 는 무조건 모든 데이터를 가져오게 되어있어서
모든 데이터를 가져온 후, filter 메서드를 이용해 데이터를 걸러 주었다.
const typeEl = document.querySelector('.type') as HTMLSelectElement;
typeEl.addEventListener('change', () => {
showElement('.loading');
renderTodoList(getElementValue('.type'), getElementValue('.order'));
hideElement('.loading');
});
export async function renderTodoList(done?: string, order?: string) {
let res = Array.from(await getListTodo()).reverse() as dataType[];
if (order === 'recent') res.sort((a, b) => +new Date(b.createdAt!) - +new Date(a.createdAt!));
else if (order === 'old') res.sort((a, b) => +new Date(a.createdAt!) - +new Date(b.createdAt!));
if (done === 'true') res = res.filter((item) => item.done === true);
else if (done === 'false') res = res.filter((item) => item.done === false);
curOrder = res.length;
setElementHtml('.todo-length', String(curOrder));
setElementHtml('.todos');
if (res.length === 0) showToast('등록된 할 일이 없습니다.');
else res.forEach((item) => renderTodo(item));
}
단일 항목 삭제 & 체크된 항목 삭제
각 할 일의 쓰레기통 아이콘을 클릭하면 단일 삭제가 가능하다.
오른쪽 상단의 체크 쓰레기통 아이콘을 클릭하면
체크된 할 일의 목록을 한 번에 삭제할 수 있다.
const checkedDeleteBtnEl = document.querySelector('.checked-delete-btn') as HTMLButtonElement;
checkedDeleteBtnEl.addEventListener('click', async () => {
if (!confirm('완료된 할 일을 모두 삭제하시겠어요?')) return;
const checkedsEl = document.querySelectorAll('.todo-done:checked');
let checkedIds: string[] = [];
if (checkedsEl) {
checkedIds = Array.from(checkedsEl).map((item) => {
if (item.parentElement && typeof item.parentElement.dataset.id === 'string') {
return item.parentElement.dataset.id;
} else {
return '';
}
});
}
if (checkedsEl.length === 0) {
alert('완료된 할 일이 없습니다.');
return;
}
checkedIds.forEach((item) => {
const todosEl = document.querySelector('.todos') as HTMLUListElement;
const todoEl = document.querySelector(`[data-id="${item}"]`) as HTMLLIElement;
todosEl.removeChild(todoEl);
});
showElement('.loading');
await deleteListTodo(checkedIds);
await fnReorderTodo();
renderTodoList();
hideElement('.loading');
showToast('삭제가 완료되었습니다.');
});
메모 수정
새로고침 시에도 값을 유지하고 싶어서
localStorage 를 이용해 구현하였다!
const memoTextareaEl = document.querySelector('.memo-txa') as HTMLTextAreaElement;
memoTextareaEl.addEventListener('change', () => {
showElement('.loading');
localStorage.setItem('memo', getElementValue('.memo-txa'));
hideElement('.loading');
showToast('메모가 저장되었습니다.');
});
타입스크립트를 프로젝트에 처음 적용해 보았는데,
자바스크립트 문법만으로 부족했던 부분들을 타입스크립트가 채워준다는 것과
엄격하게 타입을 관리하면 에러를 미리 방지할 수 있다는 것을 느끼게 되었다!
앞으로 더 규모있고 복잡한 프로젝트에서도 타입스크립트를 써보고 싶다! 😻