diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..64a14d8 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1616478436556 + + + 1616743594750 + + + 1616744018643 + + + 1616744362700 + + + 1616751914957 + + + 1616773460749 + + + 1616834916360 + + + 1616834969396 + + + 1616847328440 + + + 1616848370331 + + + 1616848810154 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 36372ce..8a256b2 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,12 @@ ## 🎯 요구사항 -- [ ] todo list에 todoItem을 키보드로 입력하여 추가하기 -- [ ] todo list의 체크박스를 클릭하여 complete 상태로 변경. (li tag 에 completed class 추가, input 태그에 checked 속성 추가) -- [ ] todo list의 x버튼을 이용해서 해당 엘리먼트를 삭제 -- [ ] todo list를 더블클릭했을 때 input 모드로 변경. (li tag 에 editing class 추가) 단 이때 수정을 완료하지 않은 상태에서 esc키를 누르면 수정되지 않은 채로 다시 view 모드로 복귀 -- [ ] todo list의 item갯수를 count한 갯수를 리스트의 하단에 보여주기 -- [ ] todo list의 상태값을 확인하여, 해야할 일과, 완료한 일을 클릭하면 해당 상태의 아이템만 보여주기 -- [ ] localStorage에 데이터를 저장하여, TodoItem의 CRUD를 반영하기. 따라서 새로고침하여도 저장된 데이터를 확인할 수 있어야 함 +- [x] todo list에 todoItem을 키보드로 입력하여 추가하기 +- [x] todo list의 체크박스를 클릭하여 complete 상태로 변경 (li tag 에 completed class 추가, input 태그에 checked 속성 추가) +- [x] todo list의 x버튼을 이용해서 해당 엘리먼트를 삭제 +- [x] todo list를 더블클릭했을 때 input 모드로 변경 (li tag 에 editing class 추가) 단 이때 수정을 완료하지 않은 상태에서 esc키를 누르면 수정되지 않은 채로 다시 view 모드로 복귀 +- [x] todo list의 item갯수를 count한 갯수를 리스트의 하단에 보여주기 +- [x] todo list의 상태값을 확인하여, 해야할 일과, 완료한 일을 클릭하면 해당 상태의 아이템만 보여주기
diff --git a/index.html b/index.html index c1d9095..b095706 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,11 @@ - + 이벤트 - TODOS +
@@ -19,16 +20,15 @@

TODOS

- 0
diff --git a/src/js/store/todoListStore.js b/src/js/store/todoListStore.js new file mode 100644 index 0000000..6a93b02 --- /dev/null +++ b/src/js/store/todoListStore.js @@ -0,0 +1,47 @@ +export {addTodoItem, updateTodoItem, removeTodoItem, findById, toggleStateTodoItem, deepCopyStore} + +const todoListStore = []; + +const EMPTY_STRING = ""; + +function deepCopyStore() { + return JSON.parse(JSON.stringify(todoListStore)); +} + +function createTodoItem(id, title = EMPTY_STRING, state = "active") { + return {id: `i-${id}`, title: title, state: state} +} + +function addTodoItem(id, title = EMPTY_STRING) { + todoListStore.push(createTodoItem(id, title)); +} + +function updateTodoItem(id, insert = EMPTY_STRING) { + findById(id).title = insert; +} + +function findById(id) { + if (todoListStore.some(item => item.id === id)) { + return todoListStore.find(item => item.id === id); + } else { + throw `${id}라는 ID를 가진 요소가 없습니다!`; + } +} + +function removeTodoItem(id) { + const index = todoListStore.findIndex(item => item.id === id); + if (index !== -1) { + todoListStore.splice(index, 1); + } else { + throw `${id}라는 ID를 가진 요소가 없습니다!`; + } +} + +function toggleStateTodoItem(id) { + const element = findById(id); + if (element.state === "completed") { + element.state = "active"; + } else { + element.state = "completed"; + } +} diff --git a/src/js/store/todoListStoreAccessor.js b/src/js/store/todoListStoreAccessor.js new file mode 100644 index 0000000..2917245 --- /dev/null +++ b/src/js/store/todoListStoreAccessor.js @@ -0,0 +1,57 @@ +import {addTodoItem, removeTodoItem, deepCopyStore, toggleStateTodoItem, updateTodoItem} from './todoListStore.js'; +import itemTemplate from "../template/todoItemTemplate.js"; +import countTemplate from "../template/todoCountTemplate.js"; + +export {execute, renderTodoList} + +const EMPTY_STRING = ""; + +function execute(command, {id, title}, state) { + switch (command) { + case "add" : + addTodoItem(id, title); + break; + case "update" : + updateTodoItem(id, title); + break; + case "delete" : + removeTodoItem(id); + break; + case "toggle" : + toggleStateTodoItem(id); + break; + default : + throw `가능한 명령 : add, update, delete, toggle / 입력된 명령: ${command}`; + } + renderTodoList(state); +} + +function renderTodoList(state) { + const todoListElement = document.querySelector(".todo-list"); + todoListElement.innerHTML = EMPTY_STRING; + visibleTotoList(state).forEach( + item => todoListElement.insertAdjacentHTML("beforeend", itemTemplate(item.id, item.title, item.state)) + ) + + const countContainerElement = document.querySelector(".count-container"); + if (countContainerElement.querySelector(".todo-count")) { + countContainerElement.querySelector(".todo-count").remove(); + } + countContainerElement.insertAdjacentHTML("afterbegin", createCountTemplate(state)); +} + +function visibleTotoList(state) { + if (state && state !== "all") { + return deepCopyStore().filter(item => item.state === state); + } else { + return deepCopyStore(); + } +} + +function createCountTemplate(state) { + if (state === "completed") { + return countTemplate(deepCopyStore().filter(item => item.state === state).length); + } else { + return countTemplate(deepCopyStore().length); + } +} \ No newline at end of file diff --git a/src/js/template/todoCountTemplate.js b/src/js/template/todoCountTemplate.js new file mode 100644 index 0000000..ec88f6e --- /dev/null +++ b/src/js/template/todoCountTemplate.js @@ -0,0 +1,4 @@ +export default function (count) { + return `${count}` +} + diff --git a/src/js/template/todoItemTemplate.js b/src/js/template/todoItemTemplate.js new file mode 100644 index 0000000..ef7dd74 --- /dev/null +++ b/src/js/template/todoItemTemplate.js @@ -0,0 +1,11 @@ +export default function itemTemplate(id, title, state) { + return `
  • +
    + + + +
    + +
  • ` +} + diff --git a/src/js/todoList.js b/src/js/todoList.js new file mode 100644 index 0000000..b9197b6 --- /dev/null +++ b/src/js/todoList.js @@ -0,0 +1,80 @@ +import {execute, renderTodoList} from './store/todoListStoreAccessor.js'; + +const $todoInput = document.querySelector("#new-todo-title"); +const $toggleParentList = document.querySelector(".todo-list"); +const $filterList = document.querySelector(".filters"); + +const EMPTY_STRING = ""; + +$todoInput.addEventListener("keyup", onAddTodoItem); + +$toggleParentList.addEventListener("keyup", onEditTodoItem) +$toggleParentList.addEventListener("click", onClickTodoItem); +$toggleParentList.addEventListener("dblclick", onEditModeTodoItem); + +$filterList.addEventListener("click", onClickFilter); + +function onAddTodoItem(event) { + const todoTitle = event.target.value; + if (event.key === "Enter" && todoTitle !== "") { + execute("add", {id: Date.now(), title: todoTitle}, getState()); + event.target.value = EMPTY_STRING; + } +} + +function onClickTodoItem(event) { + onToggleTodoItem(event); + onRemoveTodoItem(event); +} + +function onRemoveTodoItem(event) { + if (event.target && event.target.className === "destroy") { + execute("delete", {id: getOnEventClosestTodoItemId(event)}, getState()); + } +} + +function onEditModeTodoItem(event) { + event.target.closest(".todo-item").classList.add("editing"); +} + +function onEditTodoItem(event) { + if (event.target && event.target.className === "edit") { + const todoTitle = event.target.value; + + if (event.key === "Enter" && todoTitle !== "") { + execute("update", {id: getOnEventClosestTodoItemId(event), title: todoTitle}, getState()); + } else if (event.key === "Escape") { + renderTodoList(getState()); + } + } +} + +function getOnEventClosestTodoItemId(event) { + return event.target.closest(".todo-item").id; +} + +function onToggleTodoItem(event) { + if (event.target && event.target.className === "toggle") { + execute("toggle", {id: getOnEventClosestTodoItemId(event)}, getState()); + } +} + +function onClickFilter(event) { + if (event.target.classList.contains("filter-item")) { + const $filterItems = event.target.closest(".filters").querySelectorAll("li>a"); + $filterItems.forEach(item => item.classList.remove("selected")); + event.target.classList.add("selected"); + renderTodoList(getState()); + } +} + +function getState() { + let state = ""; + $filterList.querySelector(".selected").classList + .forEach(className => { + if (className !== "filter-item" && className !== "selected") { + state = className; + } + }); + return state; +} \ No newline at end of file