From 08c6542d9bfd882d4ee6928e80fef02ccfcbb519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Thu, 8 Jul 2021 07:00:35 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20:sparkles:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD1=20-=20todo=20list=EC=97=90=20todoItem?= =?UTF-8?q?=EC=9D=84=20=ED=82=A4=EB=B3=B4=EB=93=9C=EB=A1=9C=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=ED=95=98=EC=97=AC=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + index.html | 1 + src/App.js | 9 +++++++++ src/components/TodoInput.js | 28 ++++++++++++++++++++++++++++ src/components/TodoList.js | 27 +++++++++++++++++++++++++++ src/index.js | 3 +++ src/store/index.js | 22 ++++++++++++++++++++++ src/utils/selectors.js | 2 ++ 8 files changed, 93 insertions(+) create mode 100644 .gitignore create mode 100644 src/App.js create mode 100644 src/components/TodoInput.js create mode 100644 src/components/TodoList.js create mode 100644 src/index.js create mode 100644 src/store/index.js create mode 100644 src/utils/selectors.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..496ee2ca --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/index.html b/index.html index 13a02fdb..c94ff217 100644 --- a/index.html +++ b/index.html @@ -34,5 +34,6 @@

TODOS

+ diff --git a/src/App.js b/src/App.js new file mode 100644 index 00000000..005145de --- /dev/null +++ b/src/App.js @@ -0,0 +1,9 @@ +import TodoInput from "./components/TodoInput.js"; +import TodoList from "./components/TodoList.js"; +import { $ } from "./utils/selectors.js"; + +export default function App(store) { + store.addObserver(new TodoInput(store, $(".new-todo"))); + store.addObserver(new TodoList(store, $(".todo-list"))); +} + diff --git a/src/components/TodoInput.js b/src/components/TodoInput.js new file mode 100644 index 00000000..5810b8f6 --- /dev/null +++ b/src/components/TodoInput.js @@ -0,0 +1,28 @@ +import { $ } from "../utils/selectors.js"; + +const CIPHER = 1000; + +export default class TodoInput { + constructor(store, $app) { + this.store = store; + this.$app = $app; + this.mount(); + } + mount() { + this.$app.addEventListener("keypress", this.handleInputValue.bind(this)); + } + render() {} + handleInputValue(e) { + if (e.key == "Enter") { + const prevState = this.store.getState(); + const newTodo = { + id: Math.floor(Math.random() * CIPHER), + content: e.target.value, + status: "false", + }; + const newState = { ...prevState, todos: [...prevState.todos, newTodo] }; + this.store.setState(newState); + e.target.value = ""; + } + } +} diff --git a/src/components/TodoList.js b/src/components/TodoList.js new file mode 100644 index 00000000..91e2bce7 --- /dev/null +++ b/src/components/TodoList.js @@ -0,0 +1,27 @@ +import { $ } from "../utils/selectors.js"; + +export default class TodoList { + constructor(store, $app) { + this.store = store; + this.$app = $app; + this.render(); + this.mount(); + } + mount() { + } + render() { + const newState = this.store.getState(); + this.$app.innerHTML = newState.todos + .map(({ id, content, status }) => { + return `
  • +
    + + + +
    + +
  • `; + }) + .join(""); + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..41df63f9 --- /dev/null +++ b/src/index.js @@ -0,0 +1,3 @@ +import App from "./App.js"; +import Store from "./store/index.js"; +new App(new Store()); diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 00000000..1058293a --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,22 @@ +export default function Store() { + //State + this.state = { + todos: [], + status: "all", + }; + //Observer + this.observers = []; + this.addObserver = (observer) => this.observers.push(observer); + this.observing = () => + this.observers.forEach((observer) => observer.render()); + + //GET + this.getState = () => { + return this.state; + }; + //SET + this.setState = (newState) => { + this.state = { ...this.state, ...newState }; + this.observing(); + }; +} diff --git a/src/utils/selectors.js b/src/utils/selectors.js new file mode 100644 index 00000000..d0cc56db --- /dev/null +++ b/src/utils/selectors.js @@ -0,0 +1,2 @@ +export const $ = (node) => document.querySelector(node); +export const $all = (node) => document.querySelectorAll(node) \ No newline at end of file From 5e6d51e6a8548d58124a7959c419cb2ee211dc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Thu, 8 Jul 2021 16:13:22 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20:sparkles:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD2=20-=20todo=20list=EC=9D=98=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=EB=B0=95=EC=8A=A4=EB=A5=BC=20=ED=81=B4=EB=A6=AD?= =?UTF-8?q?=ED=95=98=EC=97=AC=20complete=20=EC=83=81=ED=83=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TodoList.js | 14 +++++++++++--- src/utils/helpers.js | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/utils/helpers.js diff --git a/src/components/TodoList.js b/src/components/TodoList.js index 91e2bce7..0adb7dac 100644 --- a/src/components/TodoList.js +++ b/src/components/TodoList.js @@ -1,4 +1,4 @@ -import { $ } from "../utils/selectors.js"; +import { buildNewTodo } from "../utils/helpers.js"; export default class TodoList { constructor(store, $app) { @@ -8,16 +8,24 @@ export default class TodoList { this.mount(); } mount() { + this.$app.addEventListener("click", (e) => { + const isToggle = e.target.classList.contains("toggle"); + if (isToggle) { + const newState = buildNewTodo(this.store, e); + this.store.setState(newState); + } + }); } render() { const newState = this.store.getState(); this.$app.innerHTML = newState.todos .map(({ id, content, status }) => { + const isChecked = status === "completed" ? "checked" : "false"; return `
  • - + - +
  • `; diff --git a/src/utils/helpers.js b/src/utils/helpers.js new file mode 100644 index 00000000..7c8ba4d9 --- /dev/null +++ b/src/utils/helpers.js @@ -0,0 +1,15 @@ +export function buildNewTodo(store, e) { + const prevState = store.getState(); + const targetId = e.target.getAttribute("dataset-id"); + + const newStatus = e.target.checked ? "completed" : "false"; + const newTodos = prevState.todos.map((todo) => { + if (todo.id === Number(targetId)) { + return { ...todo, status: newStatus }; + } + return todo; + }); + + const newState = { ...prevState, todos: newTodos }; + return newState; +} From 7b548bb6a45e0147d5bbc6ad58438c4b3ed683dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Thu, 8 Jul 2021 17:19:39 +0900 Subject: [PATCH 03/10] =?UTF-8?q?refactor:=20:recycle:=20todo=EC=9D=98=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TodoList.js | 16 ++++++++++------ src/utils/helpers.js | 26 +++++++++++++++++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/components/TodoList.js b/src/components/TodoList.js index 0adb7dac..0c34f7c4 100644 --- a/src/components/TodoList.js +++ b/src/components/TodoList.js @@ -1,17 +1,21 @@ -import { buildNewTodo } from "../utils/helpers.js"; +import { buildNewState } from "../utils/helpers.js"; export default class TodoList { constructor(store, $app) { this.store = store; this.$app = $app; - this.render(); this.mount(); } mount() { this.$app.addEventListener("click", (e) => { const isToggle = e.target.classList.contains("toggle"); + const isDestroy = e.target.classList.contains("destroy"); if (isToggle) { - const newState = buildNewTodo(this.store, e); + const newState = buildNewState("ADD", this.store, e); + this.store.setState(newState); + } + if (isDestroy) { + const newState = buildNewState("DELETE", this.store, e); this.store.setState(newState); } }); @@ -21,11 +25,11 @@ export default class TodoList { this.$app.innerHTML = newState.todos .map(({ id, content, status }) => { const isChecked = status === "completed" ? "checked" : "false"; - return `
  • + return `
  • - + - +
  • `; diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 7c8ba4d9..b75929cd 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -1,15 +1,31 @@ -export function buildNewTodo(store, e) { +export function buildNewState(op, store, e) { + const OPERATIONS = { + ADD: buildNewTodo, + DELETE: deleteTodo, + }; const prevState = store.getState(); - const targetId = e.target.getAttribute("dataset-id"); + const targetId = Number(e.target.closest("li").getAttribute("dataset-id")); + const newTodos = OPERATIONS[op](prevState, targetId, e); + + const newState = { ...prevState, todos: newTodos }; + return newState; +} + +export function buildNewTodo(prevState, targetId, e) { const newStatus = e.target.checked ? "completed" : "false"; const newTodos = prevState.todos.map((todo) => { - if (todo.id === Number(targetId)) { + if (todo.id === targetId) { return { ...todo, status: newStatus }; } return todo; }); + return newTodos; +} - const newState = { ...prevState, todos: newTodos }; - return newState; +export function deleteTodo(prevState, targetId) { + const newTodos = prevState.todos.filter((todo) => { + return todo.id !== targetId; + }); + return newTodos; } From 45e3e62c5bb26ce3616147797894c1d3461583d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Thu, 8 Jul 2021 19:35:10 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20:sparkles:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD4=20-=20todo=20list=EC=9D=98=20x=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9D=84=20=EC=9D=B4=EC=9A=A9=ED=95=B4=EC=84=9C=20?= =?UTF-8?q?=ED=95=B4=EB=8B=B9=20=EC=97=98=EB=A6=AC=EB=A8=BC=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TodoInput.js | 2 +- src/components/TodoList.js | 27 ++++++++++++++++++++++++--- src/utils/helpers.js | 19 ++++++++++++++++--- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/TodoInput.js b/src/components/TodoInput.js index 5810b8f6..9352b61e 100644 --- a/src/components/TodoInput.js +++ b/src/components/TodoInput.js @@ -13,7 +13,7 @@ export default class TodoInput { } render() {} handleInputValue(e) { - if (e.key == "Enter") { + if (e.key === "Enter") { const prevState = this.store.getState(); const newTodo = { id: Math.floor(Math.random() * CIPHER), diff --git a/src/components/TodoList.js b/src/components/TodoList.js index 0c34f7c4..18732bd3 100644 --- a/src/components/TodoList.js +++ b/src/components/TodoList.js @@ -1,4 +1,5 @@ import { buildNewState } from "../utils/helpers.js"; +import { $ } from "../utils/selectors.js"; export default class TodoList { constructor(store, $app) { @@ -11,7 +12,7 @@ export default class TodoList { const isToggle = e.target.classList.contains("toggle"); const isDestroy = e.target.classList.contains("destroy"); if (isToggle) { - const newState = buildNewState("ADD", this.store, e); + const newState = buildNewState("TOGGLE", this.store, e); this.store.setState(newState); } if (isDestroy) { @@ -19,13 +20,33 @@ export default class TodoList { this.store.setState(newState); } }); + this.$app.addEventListener("dblclick", (e) => { + const isList = e.target.closest("li"); + if (isList) { + isList.classList.add("editing"); + } + }); + this.$app.addEventListener("keydown", (e) => { + const isEditing = e.target.classList.contains("edit"); + + if (isEditing && e.key === "Enter") { + const newState = buildNewState("EDIT", this.store, e); + this.store.setState(newState); + e.target.closest("li").classList.remove("editing"); + } + if (isEditing && e.key === "Escape") { + const currentValue = $(".label").textContent; + e.target.value = currentValue; + e.target.closest("li").classList.remove("editing"); + } + }); } render() { const newState = this.store.getState(); this.$app.innerHTML = newState.todos - .map(({ id, content, status }) => { + .map(({ id, content, status, edit }) => { const isChecked = status === "completed" ? "checked" : "false"; - return `
  • + return `
  • diff --git a/src/utils/helpers.js b/src/utils/helpers.js index b75929cd..089af4ac 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -1,7 +1,9 @@ +//NEWSTATE export function buildNewState(op, store, e) { const OPERATIONS = { - ADD: buildNewTodo, + TOGGLE: toggleTodoStatus, DELETE: deleteTodo, + EDIT: editTodo, }; const prevState = store.getState(); const targetId = Number(e.target.closest("li").getAttribute("dataset-id")); @@ -12,7 +14,8 @@ export function buildNewState(op, store, e) { return newState; } -export function buildNewTodo(prevState, targetId, e) { +//NEWTODOS +function toggleTodoStatus(prevState, targetId, e) { const newStatus = e.target.checked ? "completed" : "false"; const newTodos = prevState.todos.map((todo) => { if (todo.id === targetId) { @@ -23,9 +26,19 @@ export function buildNewTodo(prevState, targetId, e) { return newTodos; } -export function deleteTodo(prevState, targetId) { +function deleteTodo(prevState, targetId) { const newTodos = prevState.todos.filter((todo) => { return todo.id !== targetId; }); return newTodos; } + +function editTodo(prevState, targetId, e) { + const newTodos = prevState.todos.map((todo) => { + if (todo.id === targetId) { + return { ...todo, content: e.target.value }; + } + return todo; + }); + return newTodos; +} From 48331a5b418fd1b46dd0d596789fc7352411f963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Thu, 8 Jul 2021 20:15:28 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20:sparkles:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD5=20-=20todo=20list=EC=9D=98=20item=EA=B0=AF?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20count=ED=95=9C=20=EA=B0=AF=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=9D=98=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=EC=97=90=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 4 +++- src/components/TodoTotal.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/components/TodoTotal.js diff --git a/src/App.js b/src/App.js index 005145de..724839bf 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,11 @@ import TodoInput from "./components/TodoInput.js"; import TodoList from "./components/TodoList.js"; +import TodoTotal from "./components/TodoTotal.js"; + import { $ } from "./utils/selectors.js"; export default function App(store) { store.addObserver(new TodoInput(store, $(".new-todo"))); store.addObserver(new TodoList(store, $(".todo-list"))); + store.addObserver(new TodoTotal(store, $(".todo-count"))); } - diff --git a/src/components/TodoTotal.js b/src/components/TodoTotal.js new file mode 100644 index 00000000..ed7c02b1 --- /dev/null +++ b/src/components/TodoTotal.js @@ -0,0 +1,10 @@ +export default class TodoTotal { + constructor(store, $app) { + this.store = store; + this.$app = $app; + } + render() { + const state = this.store.getState(); + this.$app.innerHTML = `총 ${state.todos.length} 개`; + } +} From 43ff3d1718394ac97bc01769dbf8f6fb553ad125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Thu, 8 Jul 2021 23:06:44 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20:sparkles:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD6=20-=20todo=20list=EC=9D=98=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EA=B0=92=EC=9D=84=20=ED=99=95=EC=9D=B8=ED=95=98?= =?UTF-8?q?=EC=97=AC=20view=20=EB=B0=94=EA=BE=B8=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 2 ++ src/components/TodoFilters.js | 22 ++++++++++++++++++++++ src/components/TodoInput.js | 4 +--- src/components/TodoList.js | 12 ++++++++---- src/components/TodoTotal.js | 9 +++++++-- src/store/index.js | 2 +- src/utils/helpers.js | 19 ++++++++++++++++++- src/utils/selectors.js | 3 ++- 8 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 src/components/TodoFilters.js diff --git a/src/App.js b/src/App.js index 724839bf..2bb277c5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,7 @@ import TodoInput from "./components/TodoInput.js"; import TodoList from "./components/TodoList.js"; import TodoTotal from "./components/TodoTotal.js"; +import TodoFilters from "./components/TodoFilters.js"; import { $ } from "./utils/selectors.js"; @@ -8,4 +9,5 @@ export default function App(store) { store.addObserver(new TodoInput(store, $(".new-todo"))); store.addObserver(new TodoList(store, $(".todo-list"))); store.addObserver(new TodoTotal(store, $(".todo-count"))); + store.addObserver(new TodoFilters(store, $(".filters"))); } diff --git a/src/components/TodoFilters.js b/src/components/TodoFilters.js new file mode 100644 index 00000000..8067291b --- /dev/null +++ b/src/components/TodoFilters.js @@ -0,0 +1,22 @@ +import { buildViewState } from "../utils/helpers.js"; +import { $, isInClassList } from "../utils/selectors.js"; + +export default class TodoList { + constructor(store, $app) { + this.store = store; + this.$app = $app; + this.mount(); + } + mount() { + this.$app.addEventListener("click", (e) => { + const isAll = isInClassList("all", e.target); + const isActive = isInClassList("active", e.target); + const isCompleted = isInClassList("completed", e.target); + const hash = e.target.hash ? e.target.hash.substring(1) : "all"; + if (isAll || isActive || isCompleted) { + buildViewState(hash, this.store, e); + } + }); + } + render() {} +} diff --git a/src/components/TodoInput.js b/src/components/TodoInput.js index 9352b61e..a1f1d2f1 100644 --- a/src/components/TodoInput.js +++ b/src/components/TodoInput.js @@ -1,5 +1,3 @@ -import { $ } from "../utils/selectors.js"; - const CIPHER = 1000; export default class TodoInput { @@ -18,7 +16,7 @@ export default class TodoInput { const newTodo = { id: Math.floor(Math.random() * CIPHER), content: e.target.value, - status: "false", + status: "active", }; const newState = { ...prevState, todos: [...prevState.todos, newTodo] }; this.store.setState(newState); diff --git a/src/components/TodoList.js b/src/components/TodoList.js index 18732bd3..9d1e7b01 100644 --- a/src/components/TodoList.js +++ b/src/components/TodoList.js @@ -1,4 +1,4 @@ -import { buildNewState } from "../utils/helpers.js"; +import { buildNewState, filterTodos } from "../utils/helpers.js"; import { $ } from "../utils/selectors.js"; export default class TodoList { @@ -28,7 +28,7 @@ export default class TodoList { }); this.$app.addEventListener("keydown", (e) => { const isEditing = e.target.classList.contains("edit"); - + if (isEditing && e.key === "Enter") { const newState = buildNewState("EDIT", this.store, e); this.store.setState(newState); @@ -42,8 +42,12 @@ export default class TodoList { }); } render() { - const newState = this.store.getState(); - this.$app.innerHTML = newState.todos + const { todos, view } = this.store.getState(); + //prettier-ignore + const curViewTodos = view === "all" ? todos + : filterTodos(todos, view); + + this.$app.innerHTML = curViewTodos .map(({ id, content, status, edit }) => { const isChecked = status === "completed" ? "checked" : "false"; return `
  • diff --git a/src/components/TodoTotal.js b/src/components/TodoTotal.js index ed7c02b1..acf1e141 100644 --- a/src/components/TodoTotal.js +++ b/src/components/TodoTotal.js @@ -1,10 +1,15 @@ +import { filterTodos } from "../utils/helpers.js"; + export default class TodoTotal { constructor(store, $app) { this.store = store; this.$app = $app; } render() { - const state = this.store.getState(); - this.$app.innerHTML = `총 ${state.todos.length} 개`; + const { view, todos } = this.store.getState(); + //prettier-ignore + const curViewTodos = view === "all" ? todos + : filterTodos(todos, view); + this.$app.innerHTML = `총 ${curViewTodos.length} 개`; } } diff --git a/src/store/index.js b/src/store/index.js index 1058293a..636e6f72 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2,7 +2,7 @@ export default function Store() { //State this.state = { todos: [], - status: "all", + view: "all", }; //Observer this.observers = []; diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 089af4ac..00354dee 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -1,3 +1,5 @@ +import { $ } from "./selectors.js"; + //NEWSTATE export function buildNewState(op, store, e) { const OPERATIONS = { @@ -16,7 +18,7 @@ export function buildNewState(op, store, e) { //NEWTODOS function toggleTodoStatus(prevState, targetId, e) { - const newStatus = e.target.checked ? "completed" : "false"; + const newStatus = e.target.checked ? "completed" : "active"; const newTodos = prevState.todos.map((todo) => { if (todo.id === targetId) { return { ...todo, status: newStatus }; @@ -42,3 +44,18 @@ function editTodo(prevState, targetId, e) { }); return newTodos; } + +export function buildViewState(op, store, e) { + $(".selected").classList.remove("selected"); + e.target.className = `${op} selected`; + + const state = store.getState(); + const newState = { ...state, view: op }; + store.setState(newState); +} + +export function filterTodos(todos, view) { + return todos.filter((todo) => { + if (todo.status === view) return todo; + }); +} diff --git a/src/utils/selectors.js b/src/utils/selectors.js index d0cc56db..e8b4ad8d 100644 --- a/src/utils/selectors.js +++ b/src/utils/selectors.js @@ -1,2 +1,3 @@ export const $ = (node) => document.querySelector(node); -export const $all = (node) => document.querySelectorAll(node) \ No newline at end of file +export const $all = (node) => document.querySelectorAll(node) +export const isInClassList = (tagName, eventTarget) => eventTarget.classList.contains(tagName) \ No newline at end of file From a7b0419f9497581f33d1540c442d224665f3fdb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Fri, 9 Jul 2021 00:56:08 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20:recycle:=20TodoList=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 2 +- src/components/TodoList.js | 64 ----------------------------- src/components/TodoList/constant.js | 10 +++++ src/components/TodoList/helper.js | 41 ++++++++++++++++++ src/components/TodoList/index.js | 29 +++++++++++++ src/utils/constants.js | 10 +++++ src/utils/helpers.js | 8 ++-- 7 files changed, 95 insertions(+), 69 deletions(-) delete mode 100644 src/components/TodoList.js create mode 100644 src/components/TodoList/constant.js create mode 100644 src/components/TodoList/helper.js create mode 100644 src/components/TodoList/index.js create mode 100644 src/utils/constants.js diff --git a/src/App.js b/src/App.js index 2bb277c5..1cadb75a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ import TodoInput from "./components/TodoInput.js"; -import TodoList from "./components/TodoList.js"; +import TodoList from "./components/TodoList/index.js"; import TodoTotal from "./components/TodoTotal.js"; import TodoFilters from "./components/TodoFilters.js"; diff --git a/src/components/TodoList.js b/src/components/TodoList.js deleted file mode 100644 index 9d1e7b01..00000000 --- a/src/components/TodoList.js +++ /dev/null @@ -1,64 +0,0 @@ -import { buildNewState, filterTodos } from "../utils/helpers.js"; -import { $ } from "../utils/selectors.js"; - -export default class TodoList { - constructor(store, $app) { - this.store = store; - this.$app = $app; - this.mount(); - } - mount() { - this.$app.addEventListener("click", (e) => { - const isToggle = e.target.classList.contains("toggle"); - const isDestroy = e.target.classList.contains("destroy"); - if (isToggle) { - const newState = buildNewState("TOGGLE", this.store, e); - this.store.setState(newState); - } - if (isDestroy) { - const newState = buildNewState("DELETE", this.store, e); - this.store.setState(newState); - } - }); - this.$app.addEventListener("dblclick", (e) => { - const isList = e.target.closest("li"); - if (isList) { - isList.classList.add("editing"); - } - }); - this.$app.addEventListener("keydown", (e) => { - const isEditing = e.target.classList.contains("edit"); - - if (isEditing && e.key === "Enter") { - const newState = buildNewState("EDIT", this.store, e); - this.store.setState(newState); - e.target.closest("li").classList.remove("editing"); - } - if (isEditing && e.key === "Escape") { - const currentValue = $(".label").textContent; - e.target.value = currentValue; - e.target.closest("li").classList.remove("editing"); - } - }); - } - render() { - const { todos, view } = this.store.getState(); - //prettier-ignore - const curViewTodos = view === "all" ? todos - : filterTodos(todos, view); - - this.$app.innerHTML = curViewTodos - .map(({ id, content, status, edit }) => { - const isChecked = status === "completed" ? "checked" : "false"; - return `
  • -
    - - - -
    - -
  • `; - }) - .join(""); - } -} diff --git a/src/components/TodoList/constant.js b/src/components/TodoList/constant.js new file mode 100644 index 00000000..31dd86be --- /dev/null +++ b/src/components/TodoList/constant.js @@ -0,0 +1,10 @@ +export const TOGGLE = "toggle"; +export const DELETE = "delete"; +export const EDIT = "edit" +export const EDITING = "editing" +export const DESTORY = "destory" +export const ENTER = "Enter" +export const ESCAPE = "Escape" +export const COMPLETED = "completed" +export const CHECKED = "checked" +export const FALSE = 'false' \ No newline at end of file diff --git a/src/components/TodoList/helper.js b/src/components/TodoList/helper.js new file mode 100644 index 00000000..0c1e634e --- /dev/null +++ b/src/components/TodoList/helper.js @@ -0,0 +1,41 @@ +//prettier-ignore +import { TOGGLE, DESTORY, DELETE, EDITING, ENTER, ESCAPE } from "./constant.js"; +import { buildNewState, filterTodos } from "../../utils/helpers.js"; +import { isInClassList } from "../../utils/selectors.js"; + +export function addsEventListener($app, store) { + $app.addEventListener("keydown", (e) => updateTodoInput(e, store)); + $app.addEventListener("click", (e) => toggleItem(e, store)); + $app.addEventListener("dblclick", (e) => setEditingMode(e)); +} +export function buildListTodos(store) { + const { todos, view } = store.getState(); + return view === "all" ? todos : filterTodos(todos, view); +} + +function toggleItem(e, store) { + const isToggle = isInClassList(TOGGLE, e.target); + const isDestroy = isInClassList(DESTORY, e.target); + if (isToggle || isDestroy) { + const op = isToggle ? TOGGLE : DELETE; + buildNewState(op, store, e); + } +} +function setEditingMode(e) { + const isList = e.target.closest("li"); + if (isList) { + isList.classList.add(EDITING); + } +} +function updateTodoInput(e, store) { + const isEditing = isInClassList(EDIT, e.target); + if (isEditing && e.key === ENTER) { + buildNewState(EDIT, store, e); + e.target.closest("li").classList.remove(EDITING); + } + if (isEditing && e.key === ESCAPE) { + const currentValue = $(".label").textContent; + e.target.value = currentValue; + e.target.closest("li").classList.remove(EDITING); + } +} diff --git a/src/components/TodoList/index.js b/src/components/TodoList/index.js new file mode 100644 index 00000000..63957079 --- /dev/null +++ b/src/components/TodoList/index.js @@ -0,0 +1,29 @@ +//prettier-ignore +import { buildListTodos, addsEventListener } from "./helper.js"; +import { COMPLETED, CHECKED, FALSE } from "./constant.js"; + +export default class TodoList { + constructor(store, $app) { + this.store = store; + this.$app = $app; + this.mount(); + } + mount() { + addsEventListener(this.$app, this.store); + } + render() { + this.$app.innerHTML = buildListTodos(this.store) + .map(({ id, content, status, edit }) => { + const isChecked = status === COMPLETED ? CHECKED : FALSE; + return `
  • +
    + + + +
    + +
  • `; + }) + .join(""); + } +} diff --git a/src/utils/constants.js b/src/utils/constants.js new file mode 100644 index 00000000..31dd86be --- /dev/null +++ b/src/utils/constants.js @@ -0,0 +1,10 @@ +export const TOGGLE = "toggle"; +export const DELETE = "delete"; +export const EDIT = "edit" +export const EDITING = "editing" +export const DESTORY = "destory" +export const ENTER = "Enter" +export const ESCAPE = "Escape" +export const COMPLETED = "completed" +export const CHECKED = "checked" +export const FALSE = 'false' \ No newline at end of file diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 00354dee..a9b923f0 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -3,9 +3,9 @@ import { $ } from "./selectors.js"; //NEWSTATE export function buildNewState(op, store, e) { const OPERATIONS = { - TOGGLE: toggleTodoStatus, - DELETE: deleteTodo, - EDIT: editTodo, + toggle: toggleTodoStatus, + delete: deleteTodo, + edit: editTodo, }; const prevState = store.getState(); const targetId = Number(e.target.closest("li").getAttribute("dataset-id")); @@ -13,7 +13,7 @@ export function buildNewState(op, store, e) { const newTodos = OPERATIONS[op](prevState, targetId, e); const newState = { ...prevState, todos: newTodos }; - return newState; + store.setState(newState); } //NEWTODOS From 07280d5739a88c1f2ee637804d3c34f9a3fef69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Fri, 9 Jul 2021 15:04:32 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20:recycle:=20deleteTodoItem=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TodoList/constant.js | 14 ++--- src/components/TodoList/helper.js | 91 ++++++++++++++++++++++------- src/components/TodoList/index.js | 9 ++- src/utils/helpers.js | 45 -------------- 4 files changed, 82 insertions(+), 77 deletions(-) diff --git a/src/components/TodoList/constant.js b/src/components/TodoList/constant.js index 31dd86be..e938e41e 100644 --- a/src/components/TodoList/constant.js +++ b/src/components/TodoList/constant.js @@ -1,10 +1,8 @@ export const TOGGLE = "toggle"; export const DELETE = "delete"; -export const EDIT = "edit" -export const EDITING = "editing" -export const DESTORY = "destory" -export const ENTER = "Enter" -export const ESCAPE = "Escape" -export const COMPLETED = "completed" -export const CHECKED = "checked" -export const FALSE = 'false' \ No newline at end of file +export const EDIT = "edit"; +export const EDITING = "editing"; +export const DESTROY = "destroy"; +export const COMPLETED = "completed"; +export const CHECKED = "checked"; +export const FALSE = "false"; diff --git a/src/components/TodoList/helper.js b/src/components/TodoList/helper.js index 0c1e634e..338e23ab 100644 --- a/src/components/TodoList/helper.js +++ b/src/components/TodoList/helper.js @@ -1,41 +1,88 @@ //prettier-ignore -import { TOGGLE, DESTORY, DELETE, EDITING, ENTER, ESCAPE } from "./constant.js"; -import { buildNewState, filterTodos } from "../../utils/helpers.js"; -import { isInClassList } from "../../utils/selectors.js"; +import { TOGGLE, DESTROY, DELETE, EDITING, EDIT } from "./constant.js"; +import { filterTodos } from "../../utils/helpers.js"; +import { $, isInClassList } from "../../utils/selectors.js"; -export function addsEventListener($app, store) { - $app.addEventListener("keydown", (e) => updateTodoInput(e, store)); - $app.addEventListener("click", (e) => toggleItem(e, store)); - $app.addEventListener("dblclick", (e) => setEditingMode(e)); -} -export function buildListTodos(store) { - const { todos, view } = store.getState(); - return view === "all" ? todos : filterTodos(todos, view); -} - -function toggleItem(e, store) { +//MOUNT HELPER +export function toggleTodoItem(e, store) { const isToggle = isInClassList(TOGGLE, e.target); - const isDestroy = isInClassList(DESTORY, e.target); - if (isToggle || isDestroy) { - const op = isToggle ? TOGGLE : DELETE; - buildNewState(op, store, e); + if (isToggle) { + buildNewState(TOGGLE, store, e); } } -function setEditingMode(e) { +export function deleteTodoItem(e, store) { + const isDestroy = isInClassList(DESTROY, e.target); + if (isDestroy) { + buildNewState(DELETE, store, e); + } +} +export function setEditingMode(e) { const isList = e.target.closest("li"); if (isList) { isList.classList.add(EDITING); } } -function updateTodoInput(e, store) { +export function editSelectedTodo(e, store) { const isEditing = isInClassList(EDIT, e.target); - if (isEditing && e.key === ENTER) { + if (isEditing && e.key === "Enter") { buildNewState(EDIT, store, e); e.target.closest("li").classList.remove(EDITING); } - if (isEditing && e.key === ESCAPE) { + if (isEditing && e.key === "Escape") { const currentValue = $(".label").textContent; e.target.value = currentValue; e.target.closest("li").classList.remove(EDITING); } } + +//VIEW HELPER +export function buildListTodos(store) { + const { todos, view } = store.getState(); + return view === "all" ? todos : filterTodos(todos, view); +} + +//STATE HELPER +function buildNewState(op, store, e) { + const OPERATIONS = { + toggle: toggleTodoStatus, + delete: deleteTodo, + edit: editTodo, + }; + const prevState = store.getState(); + const targetId = Number(e.target.closest("li").getAttribute("dataset-id")); + + const newTodos = OPERATIONS[op](prevState, targetId, e); + + const newState = { ...prevState, todos: newTodos }; + store.setState(newState); +} + +//TODO - STATUS +function toggleTodoStatus(prevState, targetId, e) { + const newStatus = e.target.checked ? "completed" : "active"; + const newTodos = prevState.todos.map((todo) => { + if (todo.id === targetId) { + return { ...todo, status: newStatus }; + } + return todo; + }); + return newTodos; +} +//TODO - DELETE +function deleteTodo(prevState, targetId) { + const newTodos = prevState.todos.filter((todo) => { + return todo.id !== targetId; + }); + return newTodos; +} + +//TODO - UPDATE +function editTodo(prevState, targetId, e) { + const newTodos = prevState.todos.map((todo) => { + if (todo.id === targetId) { + return { ...todo, content: e.target.value }; + } + return todo; + }); + return newTodos; +} diff --git a/src/components/TodoList/index.js b/src/components/TodoList/index.js index 63957079..54c19969 100644 --- a/src/components/TodoList/index.js +++ b/src/components/TodoList/index.js @@ -1,5 +1,5 @@ //prettier-ignore -import { buildListTodos, addsEventListener } from "./helper.js"; +import { buildListTodos, editSelectedTodo, toggleTodoItem, deleteTodoItem, setEditingMode } from "./helper.js"; import { COMPLETED, CHECKED, FALSE } from "./constant.js"; export default class TodoList { @@ -7,9 +7,14 @@ export default class TodoList { this.store = store; this.$app = $app; this.mount(); + this.render(); } mount() { - addsEventListener(this.$app, this.store); + //prettier-ignore + this.$app.addEventListener("keydown", (e) => editSelectedTodo(e, this.store)); + this.$app.addEventListener("dblclick", (e) => setEditingMode(e)); + this.$app.addEventListener("click", (e) => toggleTodoItem(e, this.store)); + this.$app.addEventListener("click", (e) => deleteTodoItem(e, this.store)); } render() { this.$app.innerHTML = buildListTodos(this.store) diff --git a/src/utils/helpers.js b/src/utils/helpers.js index a9b923f0..72b72441 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -1,50 +1,5 @@ import { $ } from "./selectors.js"; -//NEWSTATE -export function buildNewState(op, store, e) { - const OPERATIONS = { - toggle: toggleTodoStatus, - delete: deleteTodo, - edit: editTodo, - }; - const prevState = store.getState(); - const targetId = Number(e.target.closest("li").getAttribute("dataset-id")); - - const newTodos = OPERATIONS[op](prevState, targetId, e); - - const newState = { ...prevState, todos: newTodos }; - store.setState(newState); -} - -//NEWTODOS -function toggleTodoStatus(prevState, targetId, e) { - const newStatus = e.target.checked ? "completed" : "active"; - const newTodos = prevState.todos.map((todo) => { - if (todo.id === targetId) { - return { ...todo, status: newStatus }; - } - return todo; - }); - return newTodos; -} - -function deleteTodo(prevState, targetId) { - const newTodos = prevState.todos.filter((todo) => { - return todo.id !== targetId; - }); - return newTodos; -} - -function editTodo(prevState, targetId, e) { - const newTodos = prevState.todos.map((todo) => { - if (todo.id === targetId) { - return { ...todo, content: e.target.value }; - } - return todo; - }); - return newTodos; -} - export function buildViewState(op, store, e) { $(".selected").classList.remove("selected"); e.target.className = `${op} selected`; From e5887c596ebbf3625257c58d519ced9655929985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Fri, 9 Jul 2021 15:37:24 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20:sparkles:=20=EC=8B=AC=ED=99=94?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20-=20localStorage?= =?UTF-8?q?=EC=97=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/storage/index.js | 4 ++++ src/store/index.js | 33 ++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 src/storage/index.js diff --git a/src/storage/index.js b/src/storage/index.js new file mode 100644 index 00000000..0e47664d --- /dev/null +++ b/src/storage/index.js @@ -0,0 +1,4 @@ +export const get = (key, defaultState) => + JSON.parse(localStorage.getItem(key)) || defaultState; +export const set = (key, newState) => + localStorage.setItem(key, JSON.stringify(newState)); diff --git a/src/store/index.js b/src/store/index.js index 636e6f72..0d76fb32 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,22 +1,25 @@ -export default function Store() { - //State - this.state = { - todos: [], - view: "all", - }; - //Observer - this.observers = []; - this.addObserver = (observer) => this.observers.push(observer); - this.observing = () => - this.observers.forEach((observer) => observer.render()); +import { get, set } from "../storage/index.js"; +const USER = "user"; +export default class Store { + constructor() { + this.state = get(USER, { todos: [], view: "all" }); + this.observers = []; + } + addObserver(observer) { + this.observers.push(observer); + } + observing() { + this.observers.forEach((observer) => observer.render()); + } //GET - this.getState = () => { + getState() { return this.state; - }; + } //SET - this.setState = (newState) => { + setState(newState) { this.state = { ...this.state, ...newState }; + set(USER, this.state); this.observing(); - }; + } } From e6c0e5c12d2e68c55f22cc251f58cdef752f619a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=95=EB=82=A8?= Date: Mon, 12 Jul 2021 18:34:37 +0900 Subject: [PATCH 10/10] =?UTF-8?q?docs:=20:memo:=20README=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 502436de..0e28256a 100644 --- a/README.md +++ b/README.md @@ -39,15 +39,15 @@ ## 🎯 요구사항 -- [ ] 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의 상태값을 확인하여, 해야할 일과, 완료한 일을 클릭하면 해당 상태의 아이템만 보여주기 +- [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의 상태값을 확인하여, 해야할 일과, 완료한 일을 클릭하면 해당 상태의 아이템만 보여주기 ## 🎯🎯 심화 요구사항 -- [ ] localStorage에 데이터를 저장하여, TodoItem의 CRUD를 반영하기. 따라서 새로고침하여도 저장된 데이터를 확인할 수 있어야 함 +- [x] localStorage에 데이터를 저장하여, TodoItem의 CRUD를 반영하기. 따라서 새로고침하여도 저장된 데이터를 확인할 수 있어야 함