From 76355300a71a82be937eeb66022cea225ac35405 Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Tue, 4 Jun 2024 22:36:47 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Feat:=20MVC=20=ED=8C=A8=ED=84=B4=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EC=B6=94=EC=96=B4=20=ED=8C=8C=EC=9D=BC=20Init=20?= =?UTF-8?q?=EB=B0=8F=20model=EC=9D=98=20add,=20edit,=20delete,=20toggle=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 21 +++++++++++++++++++++ src/App.js | 17 +++++++++++++++++ src/Controller.js | 7 +++++++ src/Model.js | 36 ++++++++++++++++++++++++++++++++++++ src/View.js | 6 ++++++ src/index.js | 4 ++++ styles.css | 0 7 files changed, 91 insertions(+) create mode 100644 index.html create mode 100644 src/App.js create mode 100644 src/Controller.js create mode 100644 src/Model.js create mode 100644 src/View.js create mode 100644 src/index.js create mode 100644 styles.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..ae1b8fb --- /dev/null +++ b/index.html @@ -0,0 +1,21 @@ + + + + + + Todo + + + +
+

Todo List

+ +
+
+ +
+ + + + + diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..a7e407a --- /dev/null +++ b/src/App.js @@ -0,0 +1,17 @@ +import Controller from './Controller.js'; +import View from './view.js'; +import Model from './model.js'; + +export default function App() { + // this.storage = new Store(name); + // this.model = new Model(this.storage); + // this.template = new Template(); + // this.view = new View(this.template); + + this.model = new Model(); + this.view = new View(); + this.controller = new Controller(this.model, this.view); + +} + + diff --git a/src/Controller.js b/src/Controller.js new file mode 100644 index 0000000..a6cc3a4 --- /dev/null +++ b/src/Controller.js @@ -0,0 +1,7 @@ +export default class Controller { + constructor(view, model) { + this.view = view; + this.model = model; + } + +} diff --git a/src/Model.js b/src/Model.js new file mode 100644 index 0000000..e455344 --- /dev/null +++ b/src/Model.js @@ -0,0 +1,36 @@ +export default class Model { + constructor() { + this.todoList = []; + this.nextId = 0; + } + + add(todoText) { + const item = { + id: this.nextId++, + text: todoText, + completed: false, + } + this.todoList.push(item) + } + + edit(id, newText) { + const item = this.todoList.find(item => item.id === id); + if (item) { + item.text = newText; + } + } + + delete(id) { + const index = this.todoList.findIndex(item => item.id === id); + if (index !== -1) { + this.todoList.splice(index, 1); + } + } + + toggle(id) { + const item = this.todoList.find(item => item.id === id); + if (item) { + item.completed = !item.completed; + } + } +} diff --git a/src/View.js b/src/View.js new file mode 100644 index 0000000..e664483 --- /dev/null +++ b/src/View.js @@ -0,0 +1,6 @@ +export default class View { + constructor() { + this.$todoList = document.querySelector(".todo-list"); + this.$main = document.querySelector(".main"); + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..90d4b1d --- /dev/null +++ b/src/index.js @@ -0,0 +1,4 @@ +import App from "./App.js"; + +const app = new App(); +window.app = app; diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..e69de29 From d640f808e3405fd59d1b0c4e193749eb0b79142d Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Wed, 5 Jun 2024 16:04:58 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Feat:=20add,=20delete,=20toggle=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20view=EB=A1=9C=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 29 ++++++------- src/App.js | 9 ++-- src/Model.js | 7 +-- src/View.js | 120 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/index.js | 4 -- styles.css | 33 ++++++++++++++ 6 files changed, 173 insertions(+), 29 deletions(-) delete mode 100644 src/index.js diff --git a/index.html b/index.html index ae1b8fb..c3b21a7 100644 --- a/index.html +++ b/index.html @@ -1,21 +1,20 @@ - + - - - Todo - + + + Todo List + -
-

Todo List

- -
-
+
+

Todo List

+
+ + +
    -
    - - - + + + - diff --git a/src/App.js b/src/App.js index a7e407a..f1e1267 100644 --- a/src/App.js +++ b/src/App.js @@ -8,10 +8,11 @@ export default function App() { // this.template = new Template(); // this.view = new View(this.template); - this.model = new Model(); - this.view = new View(); - this.controller = new Controller(this.model, this.view); + this.model = new Model(); + this.view = new View(this.model); + // this.controller = new Controller(this.model, this.view); } - +const app = new App(); +window.app = app; diff --git a/src/Model.js b/src/Model.js index e455344..ef4d7ab 100644 --- a/src/Model.js +++ b/src/Model.js @@ -9,8 +9,8 @@ export default class Model { id: this.nextId++, text: todoText, completed: false, - } - this.todoList.push(item) + }; + this.todoList.push(item); } edit(id, newText) { @@ -22,8 +22,9 @@ export default class Model { delete(id) { const index = this.todoList.findIndex(item => item.id === id); + if (index !== -1) { - this.todoList.splice(index, 1); + this.todoList.splice(index, 1); } } diff --git a/src/View.js b/src/View.js index e664483..4df1ae3 100644 --- a/src/View.js +++ b/src/View.js @@ -1,6 +1,120 @@ export default class View { - constructor() { - this.$todoList = document.querySelector(".todo-list"); - this.$main = document.querySelector(".main"); + constructor(model) { + this.model = model; + + this.app = document.getElementById('root'); + + this.todoForm = document.getElementById('todo-form'); + this.todoInput = document.getElementById('todo-input'); + this.todoList = document.querySelector('.todo-list'); + + this.addEventListeners(); } + + addTodo(todoText) { + this.model.add(todoText); + this.updateView(); + } + + deleteTodo(id) { + this.model.delete(id); + this.updateView(); + } + + editTodo(id, newText) { + this.model.edit(id, newText); + this.updateView(); + } + + toggleTodo(id) { + this.model.toggle(id); + this.updateView(); + } + + updateView() { + this.todoList.innerHTML = ''; + + this.model.todoList.forEach(todo => { + const todoHTML = createTodoItem({ + id: todo.id, + text: todo.text, + completed: todo.completed ? 'completed' : '', + checked: todo.completed ? 'checked' : '', + index: todo.id + }); + this.todoList.innerHTML += todoHTML; + }); + + this.addTodoEventListeners(); + } + + + addEventListeners() + { + this.todoForm.addEventListener('submit', (e) => { + e.preventDefault(); + const todoText = this.todoInput.value.trim(); + + if (todoText !== '') { + this.addTodo(todoText); + this.todoInput.value = ''; + } + }); + } + + addTodoEventListeners() { + this.todoList.querySelectorAll('.destroy').forEach(button => { + button.addEventListener('click', (e) => { + const id = parseInt(e.target.closest('li').dataset.id); + + this.deleteTodo(id); + }); + }); + + this.todoList.querySelectorAll('label').forEach(label => { + + label.addEventListener('dblclick', (e) => { + const id = parseInt(e.target.closest('li').dataset.id); + label.contentEditable = true; + + // label.classList.add('editable'); + // 엔터 아니면 다른 부분 누르면 적용 + + }); + + label.addEventListener('change', (e) => { + console.log('change'); + label.contentEditable = false; + }); + + }); + + this.todoList.querySelectorAll('.toggle').forEach(check =>{ + check.addEventListener('change', (e)=> + { + const id = parseInt(e.target.closest('li').dataset.id); + this.toggleTodo(id); + + }); + }); + + } +} + +function createElement(tag, className) { + const element = document.createElement(tag); + if (className) element.classList.add(className); + return element; +} + +function createTodoItem({ id, text, completed, checked, index }) { + return ` +
  • +
    + + + +
    +
  • + `; } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 90d4b1d..0000000 --- a/src/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import App from "./App.js"; - -const app = new App(); -window.app = app; diff --git a/styles.css b/styles.css index e69de29..f280729 100644 --- a/styles.css +++ b/styles.css @@ -0,0 +1,33 @@ + +body { + display: flex; + align-items: center; + flex-direction: column; +} + +ul { + padding: 0; + list-style-type: none; +} + +.item { + list-style: none; +} + + +li input { + display: inline-block; +} + +.destroy{ + /* position: absolute; */ + /* right: 0px; */ +} + +.item-line{ +} + +.todo-list { + /* display: flex; */ + +} From 2f144ee7cee70a58caf8472bf856c72d5e0a94af Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Wed, 5 Jun 2024 19:38:03 +0900 Subject: [PATCH 3/9] =?UTF-8?q?Style:=20css=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- styles.css | 83 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 13 deletions(-) diff --git a/styles.css b/styles.css index f280729..fd26990 100644 --- a/styles.css +++ b/styles.css @@ -1,33 +1,90 @@ - body { + background-color: #f5f5f5; + margin: 0; + padding: 20px; display: flex; - align-items: center; flex-direction: column; + align-items: center; +} + +h1 { + color: #333; + text-align: center; +} + +#root { + width: 70%; + max-width: 400px; +} + +#todo-form { + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} + +#todo-input { + width: 80%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 16px; } -ul { +#todo-form button { + width: 10%; + background-color: #007bff; + color: white; + border: none; + border-radius: 10px; + cursor: pointer; + font-size: 16px; +} + +.todo-list { + list-style: none; padding: 0; - list-style-type: none; } .item { - list-style: none; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 5px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + margin-bottom: 10px; } +.item.completed label { + text-decoration: line-through; +} -li input { - display: inline-block; +.item-line { + display: flex; + align-items: center; + width: 100%; } -.destroy{ - /* position: absolute; */ - /* right: 0px; */ +.toggle { + margin-right: 10px; } -.item-line{ +label { + flex-grow: 1; + margin: 0 10px; } -.todo-list { - /* display: flex; */ +.destroy { + background-color: #ff6b6b; + border: none; + border-radius: 5px; + color: white; + cursor: pointer; + padding: 5px 10px; +} +.destroy:hover { + background-color: #ff5252; } From 8af0c70a1f51792f1be86bd0e695b9e1288838dc Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Thu, 6 Jun 2024 01:30:53 +0900 Subject: [PATCH 4/9] =?UTF-8?q?Feat:=20=EB=8D=94=EB=B8=94=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=EC=9C=BC=EB=A1=9C=20edit=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/View.js | 162 +++++++++++++++++++++++++++++----------------------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/src/View.js b/src/View.js index 4df1ae3..93c3f33 100644 --- a/src/View.js +++ b/src/View.js @@ -8,9 +8,25 @@ export default class View { this.todoInput = document.getElementById('todo-input'); this.todoList = document.querySelector('.todo-list'); - this.addEventListeners(); + this.addSubmitEventListeners(); } + createTodoItem({id, text, completed, checked}) { + const item = document.createElement('li'); + item.setAttribute('data-id', id); + item.className = `item ${completed}`; + + item.innerHTML = ` +
    + + + +
    + `; + + return item; + } + addTodo(todoText) { this.model.add(todoText); this.updateView(); @@ -31,25 +47,23 @@ export default class View { this.updateView(); } - updateView() { - this.todoList.innerHTML = ''; - - this.model.todoList.forEach(todo => { - const todoHTML = createTodoItem({ - id: todo.id, - text: todo.text, - completed: todo.completed ? 'completed' : '', - checked: todo.completed ? 'checked' : '', - index: todo.id - }); - this.todoList.innerHTML += todoHTML; - }); + updateView() { + this.todoList.innerHTML = ''; - this.addTodoEventListeners(); - } + this.model.todoList.forEach(todo => { + const todoItem = this.createTodoItem({ + id: todo.id, + text: todo.text, + completed: todo.completed ? 'completed' : '', + checked: todo.completed ? 'checked' : '', + }); + this.todoList.appendChild(todoItem); + }); + this.addEventListeners(); + } - addEventListeners() + addSubmitEventListeners() { this.todoForm.addEventListener('submit', (e) => { e.preventDefault(); @@ -62,59 +76,63 @@ export default class View { }); } - addTodoEventListeners() { - this.todoList.querySelectorAll('.destroy').forEach(button => { - button.addEventListener('click', (e) => { - const id = parseInt(e.target.closest('li').dataset.id); - - this.deleteTodo(id); - }); - }); - - this.todoList.querySelectorAll('label').forEach(label => { - - label.addEventListener('dblclick', (e) => { - const id = parseInt(e.target.closest('li').dataset.id); - label.contentEditable = true; - - // label.classList.add('editable'); - // 엔터 아니면 다른 부분 누르면 적용 - - }); - - label.addEventListener('change', (e) => { - console.log('change'); - label.contentEditable = false; - }); - - }); - - this.todoList.querySelectorAll('.toggle').forEach(check =>{ - check.addEventListener('change', (e)=> - { - const id = parseInt(e.target.closest('li').dataset.id); - this.toggleTodo(id); - - }); - }); - - } -} - -function createElement(tag, className) { - const element = document.createElement(tag); - if (className) element.classList.add(className); - return element; -} - -function createTodoItem({ id, text, completed, checked, index }) { - return ` -
  • -
    - - - -
    -
  • - `; + addEventListeners() { + this.addDeleteListeners(); + this.addEditListeners(); + this.addToggleListeners(); + } + + addDeleteListeners() { + this.todoList.querySelectorAll('.destroy').forEach(button => { + button.addEventListener('click', (e) => { + const id = parseInt(e.target.closest('li').dataset.id); + this.deleteTodo(id); + }); + }); + } + + addEditListeners() { + this.todoList.querySelectorAll('label').forEach(label => { + label.addEventListener('dblclick', (e) => { + this.makeLabelEditable(label); + }); + }); + } + + makeLabelEditable(label) { + const id = parseInt(label.closest('li').dataset.id); + const originalContent = label.innerText; + + label.contentEditable = true; + label.focus(); + + const finishEdit = () => { + const newText = label.innerText.trim(); + if (newText && newText !== originalContent) { + this.editTodo(id, newText); + } + label.contentEditable = false; + label.removeEventListener('blur', finishEdit); + }; + + label.addEventListener('blur', finishEdit); + + label.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + const newText = label.innerText.trim(); + if (!newText) label.innerText = originalContent; + label.blur(); + } + }); + } + + addToggleListeners() { + this.todoList.querySelectorAll('.toggle').forEach(check => { + check.addEventListener('change', (e) => { + const id = parseInt(e.target.closest('li').dataset.id); + this.toggleTodo(id); + }); + }); + } } From 887a6bdaf2b9df6c85927b1f4164416c9933f52c Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Thu, 6 Jun 2024 02:23:36 +0900 Subject: [PATCH 5/9] =?UTF-8?q?Refactor:=20viewModel=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 18 ++++++------------ src/View.js | 39 ++++++++++----------------------------- src/ViewModel.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 41 deletions(-) create mode 100644 src/ViewModel.js diff --git a/src/App.js b/src/App.js index f1e1267..4051280 100644 --- a/src/App.js +++ b/src/App.js @@ -1,17 +1,11 @@ -import Controller from './Controller.js'; -import View from './view.js'; -import Model from './model.js'; +import ViewModel from './ViewModel.js'; +import View from './View.js'; +import Model from './Model.js'; export default function App() { - // this.storage = new Store(name); - // this.model = new Model(this.storage); - // this.template = new Template(); - // this.view = new View(this.template); - - this.model = new Model(); - this.view = new View(this.model); - // this.controller = new Controller(this.model, this.view); - + this.model = new Model(); + this.viewModel = new ViewModel(this.model); + this.view = new View(this.viewModel); } const app = new App(); diff --git a/src/View.js b/src/View.js index 93c3f33..36893de 100644 --- a/src/View.js +++ b/src/View.js @@ -1,13 +1,14 @@ export default class View { - constructor(model) { - this.model = model; - + constructor(viewModel) { this.app = document.getElementById('root'); this.todoForm = document.getElementById('todo-form'); this.todoInput = document.getElementById('todo-input'); this.todoList = document.querySelector('.todo-list'); + this.viewModel = viewModel; + this.viewModel.todoListChanged = this.updateView; // callback + this.addSubmitEventListeners(); } @@ -27,30 +28,10 @@ export default class View { return item; } - addTodo(todoText) { - this.model.add(todoText); - this.updateView(); - } - - deleteTodo(id) { - this.model.delete(id); - this.updateView(); - } - - editTodo(id, newText) { - this.model.edit(id, newText); - this.updateView(); - } - - toggleTodo(id) { - this.model.toggle(id); - this.updateView(); - } - - updateView() { + updateView = (todoList) => { this.todoList.innerHTML = ''; - this.model.todoList.forEach(todo => { + todoList.forEach(todo => { const todoItem = this.createTodoItem({ id: todo.id, text: todo.text, @@ -70,7 +51,7 @@ export default class View { const todoText = this.todoInput.value.trim(); if (todoText !== '') { - this.addTodo(todoText); + this.viewModel.addTodo(todoText); this.todoInput.value = ''; } }); @@ -86,7 +67,7 @@ export default class View { this.todoList.querySelectorAll('.destroy').forEach(button => { button.addEventListener('click', (e) => { const id = parseInt(e.target.closest('li').dataset.id); - this.deleteTodo(id); + this.viewModel.deleteTodo(id); }); }); } @@ -109,7 +90,7 @@ export default class View { const finishEdit = () => { const newText = label.innerText.trim(); if (newText && newText !== originalContent) { - this.editTodo(id, newText); + this.viewModel.editTodo(id, newText); } label.contentEditable = false; label.removeEventListener('blur', finishEdit); @@ -131,7 +112,7 @@ export default class View { this.todoList.querySelectorAll('.toggle').forEach(check => { check.addEventListener('change', (e) => { const id = parseInt(e.target.closest('li').dataset.id); - this.toggleTodo(id); + this.viewModel.toggleTodo(id); }); }); } diff --git a/src/ViewModel.js b/src/ViewModel.js new file mode 100644 index 0000000..7b701de --- /dev/null +++ b/src/ViewModel.js @@ -0,0 +1,28 @@ +export default class ViewModel { + constructor(model) { + this.model = model; + this.todoList = this.model.todoList; + + this.todoListChanged = null; + } + + addTodo = (todoText) => { + this.model.add(todoText); + this.todoListChanged(this.todoList); + } + + deleteTodo = (id) => { + this.model.delete(id); + this.todoListChanged(this.todoList); + } + + editTodo = (id, newText) => { + this.model.edit(id, newText); + this.todoListChanged(this.todoList); + } + + toggleTodo = (id) => { + this.model.toggle(id); + this.todoListChanged(this.todoList); + } +} From ffde5598f7dbe08c92dc82f633b31e0869bade8b Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Thu, 6 Jun 2024 02:24:16 +0900 Subject: [PATCH 6/9] =?UTF-8?q?Chore:=20controller=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller.js | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/Controller.js diff --git a/src/Controller.js b/src/Controller.js deleted file mode 100644 index a6cc3a4..0000000 --- a/src/Controller.js +++ /dev/null @@ -1,7 +0,0 @@ -export default class Controller { - constructor(view, model) { - this.view = view; - this.model = model; - } - -} From a0770e365966cbe0172f1d6fc0896af16cfe3f7b Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Fri, 7 Jun 2024 11:47:09 +0900 Subject: [PATCH 7/9] =?UTF-8?q?Feat:=20localStorage=EC=97=90=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 --- index.html | 2 +- src/Model.js | 36 ++++++++++++++++++----- src/View.js | 82 ++++++++++++++++++++++++++++++---------------------- styles.css | 8 ++--- 4 files changed, 81 insertions(+), 47 deletions(-) diff --git a/index.html b/index.html index c3b21a7..10de558 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@

    Todo List

    - +
      diff --git a/src/Model.js b/src/Model.js index ef4d7ab..b6ffbe1 100644 --- a/src/Model.js +++ b/src/Model.js @@ -1,22 +1,40 @@ export default class Model { constructor() { - this.todoList = []; - this.nextId = 0; + this.loadLocalStorage(); + } + + loadLocalStorage() { + const storage = localStorage.getItem('todoList'); + + if (storage) { + this.todoList = JSON.parse(storage); + } else { + this.todoList = []; + } + const storageNextId = localStorage.getItem('nextId'); + this.nextId = storageNextId ? parseInt(storageNextId) : 0; + } + + updateLocalStorage() { + localStorage.setItem('todoList', JSON.stringify(this.todoList)); + localStorage.setItem('nextId', this.nextId.toString()); } add(todoText) { - const item = { - id: this.nextId++, - text: todoText, - completed: false, - }; - this.todoList.push(item); + const item = { + id: this.nextId++, + text: todoText, + completed: false, + }; + this.todoList.push(item); + this.updateLocalStorage(); } edit(id, newText) { const item = this.todoList.find(item => item.id === id); if (item) { item.text = newText; + this.updateLocalStorage(); } } @@ -25,6 +43,7 @@ export default class Model { if (index !== -1) { this.todoList.splice(index, 1); + this.updateLocalStorage(); } } @@ -32,6 +51,7 @@ export default class Model { const item = this.todoList.find(item => item.id === id); if (item) { item.completed = !item.completed; + this.updateLocalStorage(); } } } diff --git a/src/View.js b/src/View.js index 36893de..e8aa56d 100644 --- a/src/View.js +++ b/src/View.js @@ -1,59 +1,72 @@ export default class View { constructor(viewModel) { - this.app = document.getElementById('root'); + this.app = document.getElementById('root'); - this.todoForm = document.getElementById('todo-form'); - this.todoInput = document.getElementById('todo-input'); - this.todoList = document.querySelector('.todo-list'); + this.todoForm = document.getElementById('todo-form'); + this.todoInput = document.getElementById('todo-input'); + this.todoList = document.querySelector('.todo-list'); this.viewModel = viewModel; this.viewModel.todoListChanged = this.updateView; // callback - this.addSubmitEventListeners(); + this.addSubmitEventListeners(); + this.init(); } - createTodoItem({id, text, completed, checked}) { - const item = document.createElement('li'); - item.setAttribute('data-id', id); - item.className = `item ${completed}`; - - item.innerHTML = ` -
      - - - -
      - `; + init() { + const todoList = this.viewModel.todoList; + this.updateView(todoList); + } + - return item; + createTodoItem({ id, text, completed, checked }) { + return` +
    • +
      + + + +
      +
    • + `; } updateView = (todoList) => { - this.todoList.innerHTML = ''; + let checked = ''; + let unchecked = ''; todoList.forEach(todo => { const todoItem = this.createTodoItem({ - id: todo.id, - text: todo.text, - completed: todo.completed ? 'completed' : '', - checked: todo.completed ? 'checked' : '', - }); - this.todoList.appendChild(todoItem); + id: todo.id, + text: todo.text, + completed: todo.completed ? 'completed' : '', + checked: todo.completed ? 'checked' : '', + }); + + if (todo.completed) { + checked += todoItem; + } else { + unchecked += todoItem; + } }); + this.todoList.innerHTML = unchecked + checked; + this.addEventListeners(); } + addSubmitEventListeners() { - this.todoForm.addEventListener('submit', (e) => { - e.preventDefault(); - const todoText = this.todoInput.value.trim(); + this.todoForm.addEventListener('submit', (e) => { + e.preventDefault(); + + const todoText = this.todoInput.value.trim(); - if (todoText !== '') { + if (todoText !== '') { this.viewModel.addTodo(todoText); - this.todoInput.value = ''; - } + this.todoInput.value = ''; + } }); } @@ -82,14 +95,14 @@ export default class View { makeLabelEditable(label) { const id = parseInt(label.closest('li').dataset.id); - const originalContent = label.innerText; + const oldText = label.innerText; label.contentEditable = true; label.focus(); const finishEdit = () => { const newText = label.innerText.trim(); - if (newText && newText !== originalContent) { + if (newText && newText !== oldText) { this.viewModel.editTodo(id, newText); } label.contentEditable = false; @@ -101,8 +114,9 @@ export default class View { label.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); + const newText = label.innerText.trim(); - if (!newText) label.innerText = originalContent; + if (!newText) label.innerText = oldText; label.blur(); } }); diff --git a/styles.css b/styles.css index fd26990..d5c981c 100644 --- a/styles.css +++ b/styles.css @@ -33,7 +33,7 @@ h1 { #todo-form button { width: 10%; - background-color: #007bff; + background-color: #448cd8; color: white; border: none; border-radius: 10px; @@ -54,7 +54,7 @@ h1 { justify-content: space-between; align-items: center; padding: 10px; - margin-bottom: 10px; + margin: 10px 0; } .item.completed label { @@ -77,7 +77,7 @@ label { } .destroy { - background-color: #ff6b6b; + background-color: #fb7676; border: none; border-radius: 5px; color: white; @@ -86,5 +86,5 @@ label { } .destroy:hover { - background-color: #ff5252; + background-color: #e04848; } From a6fa41f2343c3f8fdc3c84f5bc7bf12fd5422dbf Mon Sep 17 00:00:00 2001 From: Jihyun Lim <74870834+irenee-14@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:27:42 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Docs:=20README.md=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 0ab8da7..70af30e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,38 @@ - (선택) 디자인 적용하기 - (선택) 무료로 배포하기 + +## 구현 요약 +- MVVM 패턴을 적용하여 Model, View, ViewModel 로 분리 +- todoList를 localStorage에 저장하여 데이터 관리 + +## 기능 + +1. 생성(Create) 및 조회 +- '할 일을 입력해주세요.' input에 입력 후 `enter` 또는 `+` 버튼을 눌러 생성 + + image + +2. 완료 상태 체크 +- 체크 박스를 클릭하여 완료 상태 체크 +- 완료된 항목은 완료되지 않은 항목 아래로 이동 + + image + +3. 수정(Update) +- 리스트의 항목을 더블클릭하여 수정 가능 + +
      + image + image +
      + +4. 삭제(Delete) +- 각 항목의 오른쪽 `X` 버튼을 클릭하여 삭제 + + image + + ## 참고 - 데이터 관리는 하단의 방식 중 하나 선택하시면 됩니다. From d3c274dcd381dde567b78b9871fb6319b604f846 Mon Sep 17 00:00:00 2001 From: irenee-14 Date: Fri, 7 Jun 2024 12:38:41 +0900 Subject: [PATCH 9/9] =?UTF-8?q?Chore:=20=EB=94=94=EB=B2=84=EA=B9=85=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/App.js b/src/App.js index 4051280..806de53 100644 --- a/src/App.js +++ b/src/App.js @@ -9,4 +9,3 @@ export default function App() { } const app = new App(); -window.app = app;