-
Notifications
You must be signed in to change notification settings - Fork 217
[10기 soonysoo] TodoList with CRUD #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: soonysoo
Are you sure you want to change the base?
Changes from all commits
5a98520
24ef8da
083007d
5cb9313
be55af2
76d96d8
023bf9e
1fb2b3a
e3074f6
11807be
ab1f0c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"liveServer.settings.port": 5501 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import Component from "./core/component.js"; | ||
import Filter from "./components/Filter.js" | ||
import Input from "./components/Input.js" | ||
import TodoList from "./components/TodoList.js" | ||
import {$, setLocalStorageItem, getLocalStorageItem} from './utils/util.js'; | ||
|
||
|
||
|
||
class App extends Component{ | ||
setup(){ | ||
const todo = getLocalStorageItem("todo")? | ||
getLocalStorageItem("todo") : setLocalStorageItem("todo", {"count": 0,"Filtermode" : 0, "List" : []}); | ||
this.$state= todo; | ||
} | ||
|
||
template(){ | ||
return ` | ||
<h1>TODOS</h1> | ||
<input id="new-todo-title" class="new-todo" | ||
placeholder="할일을 추가해주세요"autofocus | ||
/> | ||
<main id="todos"> | ||
<input class="toggle-all" type="checkbox" /> | ||
<ul id="todo-list" class="todo-list"></ul> | ||
<div id="todo-filter" class="count-container"></div> | ||
</main> | ||
` | ||
} | ||
|
||
mounted(){ | ||
const {$state ,onAddTodo, onToggleTodo, onDeleteTodo, onUpdateTodo, onFilterTodo} = this; | ||
const _Input = $('#new-todo-title'); | ||
const _TodoList = $('#todo-list'); | ||
const _Filter = $('#todo-filter'); | ||
|
||
new Input(_Input, onAddTodo.bind(this)); | ||
new TodoList(_TodoList, { | ||
$state, | ||
onToggleTodo: onToggleTodo.bind(this), | ||
onDeleteTodo : onDeleteTodo.bind(this), | ||
onUpdateTodo : onUpdateTodo.bind(this) | ||
}); | ||
new Filter(_Filter,{ | ||
$state, | ||
onFilterTodo : onFilterTodo.bind(this) | ||
}); | ||
} | ||
|
||
onAddTodo(content){ | ||
const id = String(this.$state.count*1+1); | ||
const Filtermode = this.$state.Filtermode; | ||
const List = [...this.$state.List, {id ,content:content,activate:false}]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. { id, content: content, activate: false} -> { id, content, activate: false } 다음과 같이 content 속성을 단축 할수 있을 것 같아요. (property shorthand) |
||
setLocalStorageItem("todo",{List,count : id*1,Filtermode}); | ||
this.setState(getLocalStorageItem("todo")); | ||
} | ||
onToggleTodo(id){ | ||
const List = []; | ||
this.$state.List.map(todo => { | ||
if(todo.id==id){ | ||
List.push({id:todo.id, content:todo.content, activate:!todo.activate}) | ||
}else{ | ||
List.push({id:todo.id,content:todo.content, activate:todo.activate}) | ||
} | ||
Comment on lines
+59
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. map을 사용하시면 결과값은 array입니다. 이 경우에는 forEach를 사용하여 리스트에 추가하거나 const List = this.$state.List.map(todo => {
if(todo.id==id){
return {id:todo.id, content:todo.content, activate:!todo.activate}
}
return {id:todo.id,content:todo.content, activate:todo.activate}
}) 이런식으로 작성해도 될 것 같아요! 그리고 if else보다는 if만 사용하면 가독성 측면에서 좋다고 하더라구요 |
||
}); | ||
const count = String(this.$state.count*1+1); | ||
const Filtermode = this.$state.Filtermode; | ||
setLocalStorageItem("todo",{List,count :count*1, Filtermode}) | ||
this.setState(getLocalStorageItem("todo")); | ||
|
||
} | ||
onDeleteTodo(id){ | ||
const List = this.$state.List.filter(todo => todo.id!==id); | ||
const count = String(this.$state.count*1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *1 은 형변환하려고 하실 때 말고 다른 용도로 사용한 것인지 궁금해요. 😀 |
||
const Filtermode = this.$state.Filtermode; | ||
setLocalStorageItem("todo",{List,count :count*1, Filtermode}) | ||
this.setState(getLocalStorageItem("todo")); | ||
} | ||
onUpdateTodo(id, new_content){ | ||
const List = []; | ||
this.$state.List.map(todo => { | ||
if(todo.id==id){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이것도 스타일일 수 있는데요. 보통 if문과 (사이 는 공백 한칸 띄우는 스타일을 요즘 많이 사용하는 분위기 더라고요. 그래서 |
||
List.push({id:todo.id, content:new_content, activate:todo.activate}) | ||
}else{ | ||
List.push({id:todo.id,content:todo.content, activate:todo.activate}) | ||
} | ||
}); | ||
const count = String(this.$state.count*1); | ||
const Filtermode = this.$state.Filtermode; | ||
setLocalStorageItem("todo",{List,count :count*1, Filtermode}) | ||
this.setState(getLocalStorageItem("todo")); | ||
} | ||
onFilterTodo(mode){ | ||
const List = this.$state.List; | ||
const count = String(this.$state.count*1); | ||
const Filtermode = mode; | ||
setLocalStorageItem("todo",{List,count :count*1, Filtermode}) | ||
this.setState(getLocalStorageItem("todo")); | ||
} | ||
} | ||
|
||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import Component from "../core/component.js"; | ||
import {$$} from '../utils/util.js'; | ||
|
||
class Filter extends Component{ | ||
setup(){ | ||
this.$state = this.$props.$state; | ||
} | ||
|
||
template(){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시, 전체를 템플릿으로 한 부분이 궁금해요. ^^ |
||
const mode = this.$state.Filtermode; | ||
const totalNum = ((mode)=>{ | ||
if(mode==0){ | ||
return this.$state.List.length; | ||
}else if(mode==1){ | ||
return this.$state.List.filter(item => item.activate!=true).length; | ||
}else{ | ||
return this.$state.List.filter(item => item.activate!=false).length; | ||
} | ||
})(mode) | ||
return ` | ||
<span class="todo-count">총 <strong>${totalNum}</strong> 개</span> | ||
<ul class="filters"> | ||
<li> | ||
<a class="all ${mode==0?"selected":""}" id="0" href="#">전체보기</a> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
const type = (mode) => {
return mode === 0 ? "selected" : ""
} 이런식으로 뺀 다음에 해당 코드에서는 ${type(mode)} 이런식으로 작성해도 좋지 않을까요?
|
||
</li> | ||
<li> | ||
<a class="active ${mode==1?"selected":""}" id="1" href="#active">해야할 일</a> | ||
</li> | ||
<li> | ||
<a class="completed ${mode==2?"selected":""}" id="2" href="#completed">완료한 일</a> | ||
</li> | ||
</ul> | ||
` | ||
} | ||
mounted(){ | ||
const filterBtn = $$('.filters > li > a'); | ||
filterBtn.forEach(element =>{ | ||
element.addEventListener('click',(e)=>{ | ||
this.$props.onFilterTodo(e.target.id); | ||
}) | ||
}); | ||
} | ||
} | ||
|
||
export default Filter; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import Component from "../core/component.js"; | ||
|
||
class Input extends Component{ | ||
setEvent(){ | ||
const onAddTodo = this.$props; | ||
this.$target.addEventListener('keyup',(e)=>{ | ||
if(e.key=="Enter"){ | ||
const content = this.$target.value; | ||
onAddTodo(content); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
export default Input; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import Component from "../core/component.js"; | ||
import {$$} from "../utils/util.js"; | ||
|
||
class TodoList extends Component{ | ||
setup(){ | ||
this.$state = this.$props.$state; | ||
} | ||
|
||
template(){ | ||
const todoList = this.$state; | ||
return ` | ||
${todoList.List.map(item =>` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. { id, content, activate } 구조분해 할당을 이용해도 좋을 것 같아요! |
||
<li data-id="${item.id}" class=${item.activate?"completed":"notcompleted"}> | ||
<div class="view"> | ||
<input class="toggle" id=${item.id} type="checkbox" ${item.activate?"checked":""}/> | ||
<label id=${item.id} class="label">${item.content}</label> | ||
<button id=${item.id} class="destroy"></button> | ||
</div> | ||
<input id=${item.id} class="edit" value="${item.content}" /> | ||
</li> | ||
`).join('')} | ||
` | ||
Comment on lines
+12
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 분해할당을 적극적으로 사용해보세요!! todoList.List.map(({ id, activate, content }) => {
#code
}) 이런식으로 작성하면 굳이 item. 을 안붙여도 되기 때문에 가독성 측면에서 좋을 것 같아요 |
||
} | ||
mounted(){ | ||
const deleteBtns = $$('.destroy'); | ||
deleteBtns.forEach(element => { | ||
element.addEventListener('click',(e)=>{ | ||
this.$props.onDeleteTodo(e.target.id); | ||
}) | ||
}); | ||
|
||
const toggleBtn = $$('.toggle'); | ||
toggleBtn.forEach(element => { | ||
element.addEventListener('click',(e)=>{ | ||
this.$props.onToggleTodo(e.target.id); | ||
}) | ||
}); | ||
|
||
const editBtn = $$('.label'); | ||
editBtn.forEach(element =>{ | ||
element.addEventListener('dblclick', (e)=>{ | ||
this.editTodo(e.target); | ||
}) | ||
}); | ||
} | ||
editTodo(targetDom){ | ||
const edit_li = targetDom.parentNode.parentNode; | ||
const edit_input = targetDom.parentNode.nextElementSibling; | ||
Comment on lines
+47
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 변수명을 스네이크 케이스로 작성하신 이유가 있으신가요?.? |
||
edit_li.classList.add('editing'); | ||
|
||
if(edit_li.classList.contains("editing")){ | ||
edit_li.addEventListener('keyup',(e)=>{ | ||
if(e.key=="Enter"){ | ||
this.$props.onUpdateTodo(targetDom.id, edit_input.value); | ||
} | ||
if(e.key=="Escape"){ | ||
edit_li.classList.remove('editing'); | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
|
||
export default TodoList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export default class Component { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 공통된 컴포넌트의 사용이 좋네요!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 하는 것이 모던한 JS 스타일인 것 같아요. 제가 이전에 본 장후님의 코드도 데이터 관리 부분, 그리는 부분, 컨트롤 하는 부분이 나누어 진 것을 봤어요. 마찬가지로 수은님도 그런 render(그리는 부분), setState(데이터 관리) 부분을 확인할 수 있어서 좋았습니다. 저도 앞으로 이렇게 해야 겠습니다. 😀 |
||
$target; | ||
$props; | ||
$state; | ||
constructor ($target, $props) { | ||
this.$target = $target; | ||
this.$props = $props; | ||
this.setup(); | ||
this.setEvent(); | ||
this.render(); | ||
} | ||
setup () {}; | ||
template () { return ''; } | ||
render () { | ||
this.$target.innerHTML = this.template(); | ||
this.mounted(); | ||
} | ||
setEvent () {} | ||
mounted(){} | ||
setState (List) { | ||
this.$state = List; | ||
this.render(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import App from './App.js'; | ||
|
||
new App(document.querySelector('.todoapp')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. id에서 class에서 바꾼 의도가 있을 것 같은데요. 궁금해요. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const $ = (selector) => document.querySelector(selector); | ||
const $$ = (selector) => document.querySelectorAll(selector); | ||
const setLocalStorageItem = (key, value) => localStorage.setItem(key, JSON.stringify(value)); | ||
const getLocalStorageItem = (key) => { | ||
const todoList = localStorage.getItem(key); | ||
return JSON.parse(todoList); | ||
}; | ||
|
||
export {$, $$, setLocalStorageItem, getLocalStorageItem}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컴포넌트의 분리가 좋네요!