-
Notifications
You must be signed in to change notification settings - Fork 217
[10기 김장후] TodoList with CRUD #216
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: kimjanghu
Are you sure you want to change the base?
Changes from all commits
0355050
3fda74c
0fb7a79
7c73ad4
5da65a2
1f04215
86965d7
fabc69e
58bb086
442933a
57aea54
12a311c
d69a150
b4592f1
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,13 @@ | ||
module.exports = { | ||
env: { | ||
browser: true, | ||
node: true, | ||
es2021: true, | ||
}, | ||
plugins: ["prettier"], | ||
extends: ["eslint:recommended", "plugin:prettier/recommended"], | ||
parserOptions: { | ||
ecmaVersion: 2021, | ||
sourceType: "module", | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
|
||
# Created by https://www.toptal.com/developers/gitignore/api/node | ||
# Edit at https://www.toptal.com/developers/gitignore?templates=node | ||
|
||
### Node ### | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
lerna-debug.log* | ||
.pnpm-debug.log* | ||
|
||
# Diagnostic reports (https://nodejs.org/api/report.html) | ||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
*.pid.lock | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
*.lcov | ||
|
||
# nyc test coverage | ||
.nyc_output | ||
|
||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# Bower dependency directory (https://bower.io/) | ||
bower_components | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (https://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directories | ||
node_modules/ | ||
jspm_packages/ | ||
|
||
# Snowpack dependency directory (https://snowpack.dev/) | ||
web_modules/ | ||
|
||
# TypeScript cache | ||
*.tsbuildinfo | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional eslint cache | ||
.eslintcache | ||
|
||
# Microbundle cache | ||
.rpt2_cache/ | ||
.rts2_cache_cjs/ | ||
.rts2_cache_es/ | ||
.rts2_cache_umd/ | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
||
# Output of 'npm pack' | ||
*.tgz | ||
|
||
# Yarn Integrity file | ||
.yarn-integrity | ||
|
||
# dotenv environment variables file | ||
.env | ||
.env.test | ||
.env.production | ||
|
||
# parcel-bundler cache (https://parceljs.org/) | ||
.cache | ||
.parcel-cache | ||
|
||
# Next.js build output | ||
.next | ||
out | ||
|
||
# Nuxt.js build / generate output | ||
.nuxt | ||
dist | ||
|
||
# Gatsby files | ||
.cache/ | ||
# Comment in the public line in if your project uses Gatsby and not Next.js | ||
# https://nextjs.org/blog/next-9-1#public-directory-support | ||
# public | ||
|
||
# vuepress build output | ||
.vuepress/dist | ||
|
||
# Serverless directories | ||
.serverless/ | ||
|
||
# FuseBox cache | ||
.fusebox/ | ||
|
||
# DynamoDB Local files | ||
.dynamodb/ | ||
|
||
# TernJS port file | ||
.tern-port | ||
|
||
# Stores VSCode versions used for testing VSCode extensions | ||
.vscode-test | ||
|
||
# yarn v2 | ||
.yarn/cache | ||
.yarn/unplugged | ||
.yarn/build-state.yml | ||
.yarn/install-state.gz | ||
.pnp.* | ||
|
||
# End of https://www.toptal.com/developers/gitignore/api/node |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module.exports = { | ||
singleQuote: true, | ||
semi: true, | ||
useTabs: false, | ||
tabWidth: 2, | ||
trailingComma: 'all', | ||
printWidth: 120, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,37 @@ | ||
<!DOCTYPE html> | ||
<html lang="kr"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<title>이벤트 - TODOS</title> | ||
<link rel="stylesheet" href="./src/css/style.css" /> | ||
</head> | ||
<body> | ||
<div class="todoapp"> | ||
<h1>TODOS</h1> | ||
<input | ||
id="new-todo-title" | ||
class="new-todo" | ||
placeholder="할일을 추가해주세요" | ||
autofocus | ||
/> | ||
<main> | ||
<input class="toggle-all" type="checkbox" /> | ||
<ul id="todo-list" class="todo-list"></ul> | ||
<div class="count-container"> | ||
<span class="todo-count">총 <strong>0</strong> 개</span> | ||
<ul class="filters"> | ||
<li> | ||
<a class="all selected" href="#">전체보기</a> | ||
</li> | ||
<li> | ||
<a class="active" href="#active">해야할 일</a> | ||
</li> | ||
<li> | ||
<a class="completed" href="#completed">완료한 일</a> | ||
</li> | ||
</ul> | ||
</div> | ||
</main> | ||
</div> | ||
</body> | ||
</html> | ||
|
||
<head> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<title>이벤트 - TODOS</title> | ||
<link rel="stylesheet" href="./src/css/style.css" /> | ||
<script type="module" src="./src/index.js" defer></script> | ||
</head> | ||
|
||
<body> | ||
<div id="app" class="todoapp"> | ||
<h1>TODOS</h1> | ||
<input id="new-todo-title" class="new-todo" placeholder="할일을 추가해주세요" autofocus /> | ||
<main> | ||
<input class="toggle-all" type="checkbox" /> | ||
<ul id="todo-list" class="todo-list"></ul> | ||
<div class="count-container"> | ||
<span class="todo-count"></span> | ||
<ul class="filters"> | ||
<li> | ||
<a class="all selected" href="#">전체보기</a> | ||
</li> | ||
<li> | ||
<a class="active" href="#active">해야할 일</a> | ||
</li> | ||
<li> | ||
<a class="completed" href="#completed">완료한 일</a> | ||
</li> | ||
</ul> | ||
</div> | ||
</main> | ||
</div> | ||
</body> | ||
|
||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "js-todo-list-step1", | ||
"version": "1.0.0", | ||
"description": "<p align=\"middle\" > <img width=\"200px;\" src=\"./src/images/check_list.png\"/> </p> <h2 align=\"middle\">JS 투두리스트 스텝1</h2> <p align=\"middle\">자바스크립트로 구현 하는 투두리스트</p> <p align=\"middle\"> <img src=\"https://img.shields.io/badge/version-1.0.0-blue?style=flat-square\" alt=\"template version\"/> <img src=\"https://img.shields.io/badge/language-html-red.svg?style=flat-square\"/> <img src=\"https://img.shields.io/badge/language-css-blue.svg?style=flat-square\"/> <img src=\"https://img.shields.io/badge/language-js-yellow.svg?style=flat-square\"/> <a href=\"https://github.com/next-step/js-todo-list-step1/blob/main/LICENSE\" target=\"_blank\"> <img src=\"https://img.shields.io/github/license/next-step/js-todo-list-step1.svg?style=flat-square&label=license&color=08CE5D\"/> </a> </p>", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/kimjanghu/js-todo-list-step1.git" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/kimjanghu/js-todo-list-step1/issues" | ||
}, | ||
"homepage": "https://github.com/kimjanghu/js-todo-list-step1#readme", | ||
"devDependencies": { | ||
"eslint": "^7.30.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"prettier": "^2.3.2" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import NewTodoInput from './components/NewTodoInput.js'; | ||
import TodoCount from './components/TodoCount.js'; | ||
import TodoList from './components/TodoList.js'; | ||
import Component from './core/component.js'; | ||
import State from './core/State.js'; | ||
import { FILTER_TYPES, STORAGE_KEY } from './utils/constants.js'; | ||
import { $, setLocalStorageItem, getLocalStorageItem } from './utils/utils.js'; | ||
|
||
export default class App extends Component { | ||
setState() { | ||
this.todoList = new State(getLocalStorageItem(STORAGE_KEY.TODO) || []); | ||
this.filteredTodoList = new State(null); | ||
this.filterState = new State('all'); | ||
} | ||
|
||
render() { | ||
this.mountChildren(); | ||
this.mountTodoList(); | ||
} | ||
|
||
setFilteredTodoList() { | ||
const filterTodo = Object.freeze({ | ||
[FILTER_TYPES.ALL]: this.viewAllTodo.bind(this), | ||
[FILTER_TYPES.ACTIVE]: this.viewActiveTodo.bind(this), | ||
[FILTER_TYPES.COMPLETED]: this.viewCompletedTodo.bind(this), | ||
}); | ||
filterTodo[this.filterState.get()](); | ||
// this.todoListView.setState(this.filteredTodoList.get()); | ||
// this.todoListView.render(); | ||
this.renderComponent(this.todoListView); | ||
this.renderComponent(this.todoCountView); | ||
this.todoCountView.setState(this.filteredTodoList.get()); | ||
this.todoCountView.render(); | ||
} | ||
|
||
renderComponent(view) { | ||
view.setState(this.filteredTodoList.get()); | ||
view.render(); | ||
} | ||
|
||
viewAllTodo() { | ||
this.filteredTodoList.set(this.todoList.get()); | ||
} | ||
|
||
viewActiveTodo() { | ||
const activeTodo = this.todoList.get().filter((todo) => todo.checked === false); | ||
this.filteredTodoList.set(activeTodo); | ||
} | ||
|
||
viewCompletedTodo() { | ||
const completedTodo = this.todoList.get().filter((todo) => todo.checked === true); | ||
this.filteredTodoList.set(completedTodo); | ||
} | ||
|
||
mountChildren() { | ||
new NewTodoInput($('#new-todo-title'), { | ||
todoList: this.todoList, | ||
onSubmitTodo: this.mountTodoList.bind(this), | ||
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. ^^ 이렇게 바꾼 이유는 혹시 onSubmitTodo를 이 App.js 내부에서는 그려짐과 동시에 행해야 할 것이기에 명칭을 바꾼 것으로 생각됩니다만, key값은 그대로 두신 이유가 궁금해졌습니다. 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. props를 받는 컴포넌트의 네임을 바꾸기 귀찮아서 그대로 뒀습니다 ㅎㅎ 큰 이유는 없었어요 |
||
}); | ||
} | ||
|
||
mountTodoList() { | ||
this.todoListView = new TodoList($('#todo-list'), { | ||
todoList: this.filteredTodoList.get() === null ? this.todoList.get() : this.filteredTodoList.get(), | ||
checkTodo: this.checkTodo.bind(this), | ||
deleteTodo: this.deleteTodo.bind(this), | ||
editTodo: this.editTodo.bind(this), | ||
}); | ||
|
||
this.todoCountView = new TodoCount($('.count-container'), { | ||
todoList: this.filteredTodoList.get() === null ? this.todoList.get() : this.filteredTodoList.get(), | ||
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: this.filteredTodoList.get() ?? this.todoList.get(), Nullish coalescing operator를 이용해 볼 수도 있을 것 같아요. 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. 감사합니다! 한번 찾아보고 적용해보겠습니다! |
||
filterState: this.filterState, | ||
onClickTodoRender: this.setFilteredTodoList.bind(this), | ||
}); | ||
} | ||
|
||
checkTodo(id) { | ||
const todoList = this.todoList.get().map((todo) => { | ||
if (todo.id === id) { | ||
todo.checked = !todo.checked; | ||
return todo; | ||
} | ||
return todo; | ||
}); | ||
this.todoList.set([...todoList]); | ||
setLocalStorageItem(STORAGE_KEY, todoList); | ||
} | ||
|
||
deleteTodo(id) { | ||
const todoList = this.todoList.get().filter((todo) => todo.id !== id); | ||
setLocalStorageItem(STORAGE_KEY.TODO, todoList); | ||
this.todoList.set([...todoList]); | ||
this.todoCountView.render(); | ||
} | ||
|
||
editTodo(id, value) { | ||
const todoList = this.todoList.get().map((todo) => { | ||
if (todo.id === id) { | ||
todo.todo = value; | ||
return todo; | ||
} | ||
return todo; | ||
Comment on lines
+98
to
+102
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. return을 두 번 작성하신 건 가독성 때문일까요? 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. 네 어차피 todo.id와 id가 같은지 다른지 2가지 경우가 생기기 때문에 저런식으로 작성했습니다. |
||
}); | ||
setLocalStorageItem(STORAGE_KEY.TODO, todoList); | ||
this.todoList.set([...todoList]); | ||
this.todoListView.render(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import Component from '../core/component.js'; | ||
import { STORAGE_KEY } from '../utils/constants.js'; | ||
import { setLocalStorageItem } from '../utils/utils.js'; | ||
|
||
export default class NewTodoInput extends Component { | ||
bindEvents() { | ||
this.$target.addEventListener('keyup', ({ key }) => { | ||
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. (질문) 여기서 ({ key }) 이렇게 하는 것과 ( key ) 이렇게 하는 것은 차이가 없겠지요? 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. 음 destructuring 키워드를 찾아보시면 될 것같아요! 차이는 없지만 전 가독성 측면에서 destructuring을 즐겨써요 |
||
if (key !== 'Enter') return; | ||
this.addTodo(this.$target.value); | ||
this.$target.value = ''; | ||
this.props.onSubmitTodo(); | ||
}); | ||
} | ||
|
||
addTodo(todo) { | ||
const todoList = this.props.todoList.get(); | ||
todoList.push({ | ||
id: Date.now(), | ||
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로 사용하는 todo프로젝트이니 now로도 id가 중복될 일은 없겠네요! |
||
todo, | ||
checked: false, | ||
}); | ||
this.props.todoList.set(todoList); | ||
Comment on lines
+17
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. newTodoList = [...todoList, {
id: Date.now(),
todo,
checked: false,
}]; push로 배열의 상태를 바꾸는게 나을지 펼침연산자 이용해 새로운 리스트를 만드는게 좋을지 고민이 되네요. 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. 펼침연산자를 사용하는게 더 좋아보입니다! (알고리즘을 제외하고) 최대한 원본배열을 건들지않고 작업하는게 유지보수나 디버깅 측면에서 유리하다고 들었습니다. 좋은 케이스 감사합니다. |
||
setLocalStorageItem(STORAGE_KEY.TODO, todoList); | ||
} | ||
} |
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.
저는 프론트엔드 공부가 부족해서 그런데, 이런식으로 해서 바닐라 JS만으로도 JS 분리를 깔끔하게 되는 것 같아요.
좋은 경험 얻어 갑니다. 👍