From 1d79551c7e9c7e33e8194c4bcf692ded96ee2290 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 19:05:29 +0900 Subject: [PATCH 01/18] feat(boilerplate): init boilerplate --- mission002/eastjun/css/style.css | 334 +++++++++++++++++++++++++++++++ mission002/eastjun/index.html | 40 ++++ mission002/eastjun/js/App.js | 50 +++++ 3 files changed, 424 insertions(+) create mode 100644 mission002/eastjun/css/style.css create mode 100644 mission002/eastjun/index.html create mode 100644 mission002/eastjun/js/App.js diff --git a/mission002/eastjun/css/style.css b/mission002/eastjun/css/style.css new file mode 100644 index 0000000..14a905e --- /dev/null +++ b/mission002/eastjun/css/style.css @@ -0,0 +1,334 @@ +html, +body { + margin: 0; + padding: 10px; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; + -webkit-appearance: none; + appearance: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #4d4d4d; + min-width: 230px; + max-width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 300; +} + +:focus { + outline: 0; +} + +.hidden { + display: none; +} + +.todoapp { + background: #fff; + margin: 130px 0 40px 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.1); +} + +.todoapp input::-webkit-input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp h1 { + position: absolute; + top: -125px; + width: 100%; + font-size: 60px; + text-align: center; + color: dimgray; + font-weight: 100; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; +} + +.new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + font-weight: inherit; + line-height: 1.4em; + border: 0; + color: inherit; + padding: 6px; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); +} + +.main { + position: relative; + z-index: 2; + border-top: 1px solid #e6e6e6; +} + +.toggle-all { + width: 1px; + height: 1px; + border: none; + opacity: 0; + position: absolute; + right: 100%; + bottom: 100%; +} + +.toggle-all + label { + width: 60px; + height: 34px; + font-size: 0; + position: absolute; + top: -52px; + left: -13px; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.toggle-all + label:before { + content: '❯'; + font-size: 22px; + color: #e6e6e6; + padding: 10px 27px 10px 27px; +} + +.toggle-all:checked + label:before { + color: #737373; +} + +.todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +.todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px solid #ededed; +} + +.todo-list li:last-child { + border-bottom: none; +} + +.todo-list li.editing { + border-bottom: none; + padding: 0; +} + +.todo-list li.editing .edit { + display: block; + width: calc(100% - 43px); + padding: 12px 16px; + margin: 0 0 0 43px; +} + +.todo-list li.editing .view { + display: none; +} + +.todo-list li .toggle { + text-align: center; + width: 40px; + height: auto; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; + -webkit-appearance: none; + appearance: none; +} + +.todo-list li .toggle { + opacity: 0; +} + +.todo-list li .toggle + label { + background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); + background-repeat: no-repeat; + background-position: center left; +} + +.todo-list li .toggle:checked + label { + background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); +} + +.todo-list li label { + word-break: break-all; + padding: 15px 15px 15px 60px; + display: block; + line-height: 1.2; + transition: color 0.4s; +} + +.todo-list li.completed label { + color: #d9d9d9; + text-decoration: line-through; +} + +.todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: #cc9a9a; + margin-bottom: 11px; + transition: color 0.2s ease-out; + cursor: pointer; +} + +.todo-list li .destroy:hover { + color: #af5b5e; +} + +.todo-list li .destroy:after { + content: '×'; +} + +.todo-list li:hover .destroy { + display: block; +} + +.todo-list li .edit { + display: none; +} + +.todo-list li.editing:last-child { + margin-bottom: -1px; +} + +.count-container { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + border-top: 1px solid #e6e6e6; +} + +.count-container:before { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50px; + overflow: hidden; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), + 0 8px 0 -3px #f6f6f6, + 0 9px 1px -3px rgba(0, 0, 0, 0.2), + 0 16px 0 -6px #f6f6f6, + 0 17px 2px -6px rgba(0, 0, 0, 0.2); +} + +.todo-count { + float: left; + text-align: left; +} + +.todo-count strong { + font-weight: 300; +} + +.filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +.filters li { + display: inline; +} + +.filters li a { + color: inherit; + margin: 3px; + padding: 3px 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; +} + +.filters li a:hover { + border-color: rgba(175, 47, 47, 0.1); +} + +.filters li a.selected { + border-color: rgba(175, 47, 47, 0.2); +} + +.clear-completed, html .clear-completed:active { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + cursor: pointer; +} + +.clear-completed:hover { + text-decoration: underline; +} + +.info { + margin: 65px auto 0; + color: #bfbfbf; + font-size: 10px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-align: center; +} + +.info p { + line-height: 1; +} + +.info a { + color: inherit; + text-decoration: none; + font-weight: 400; +} + +.info a:hover { + text-decoration: underline; +} diff --git a/mission002/eastjun/index.html b/mission002/eastjun/index.html new file mode 100644 index 0000000..f996bd9 --- /dev/null +++ b/mission002/eastjun/index.html @@ -0,0 +1,40 @@ + + + + + + 이벤트 - TODOS + + + +
+
+

TODOS

+ +
+
+ +
    +
    +
    + 총 + + + 개 + + +
    +
    + + + diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js new file mode 100644 index 0000000..5a44093 --- /dev/null +++ b/mission002/eastjun/js/App.js @@ -0,0 +1,50 @@ +import TodoList from './components/TodoList.js' +import TodoInput from './components/TodoInput.js' +import TodoCount from './components/TodoCount.js' +import TodoStatus from './components/TodoStatus.js' +import api from './api/api.js' + +function TodoApp() { + this.todoItems = [] + + this.setState = (updatedItems) => { + this.todoItems = updatedItems + todoList.setState(this.todoItems) + todoCount.setState(this.todoItems) + } + + new TodoInput({ + setState: (todoItems) => { + this.todoItems = todoItems + this.setState(this.todoItems) + } + }) + + new TodoStatus({ + todoItems: this.todoItems, + filter: (filteredItems) => { + this.setState(filteredItems) + } + }) + + const todoList = new TodoList({ + setState: (todoItems) => { + this.todoItems = todoItems + this.setState(this.todoItems) + }, + onToggleItem: (index) => { + this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted + this.setState(this.todoItems) + }, + onRemoveItem: (index) => { + this.todoItems.splice(index, 1) + this.setState(this.todoItems) + } + }) + + const todoCount = new TodoCount({ + todoItems: this.todoItems + }) +} + +new TodoApp() From 91f410f38ffbe1fc415b227f1a47af7d05510096 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 19:07:09 +0900 Subject: [PATCH 02/18] feat(todoInput): apply add & get api --- mission002/eastjun/js/api/api.js | 49 +++++++++++++++++++ mission002/eastjun/js/components/TodoInput.js | 38 ++++++++++++++ mission002/eastjun/js/components/TodoItem.js | 4 ++ mission002/eastjun/js/utils/templates.js | 13 +++++ 4 files changed, 104 insertions(+) create mode 100644 mission002/eastjun/js/api/api.js create mode 100644 mission002/eastjun/js/components/TodoInput.js create mode 100644 mission002/eastjun/js/components/TodoItem.js create mode 100644 mission002/eastjun/js/utils/templates.js diff --git a/mission002/eastjun/js/api/api.js b/mission002/eastjun/js/api/api.js new file mode 100644 index 0000000..8ae0d3b --- /dev/null +++ b/mission002/eastjun/js/api/api.js @@ -0,0 +1,49 @@ +const DOMAIN = 'http://todo-api.roto.codes/' + +const USERNAME = 'eastjun' + +const METHOD = { + PUT() { + return { + method: 'PUT', + } + }, + DELETE() { + return { + method: 'DELETE', + } + }, + POST(data) { + return { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + content: data, + }), + } + }, +} + +const api = (() => { + const request = (uri, config) => fetch(uri, config).then((data) => data.json()) + + const todoItem = { + get: () => request(`${DOMAIN}${USERNAME}`), + add: (data) => request(`${DOMAIN}${USERNAME}`, METHOD.POST(data)), + complete: (id) => request(`${DOMAIN}${USERNAME}/${id}/toggle`, METHOD.PUT), + remove: (id) => request(`${DOMAIN}${USERNAME}/${id}`, METHOD.DELETE), + } + + const user = { + get: () => request(`${DOMAIN}users`), + } + + return { + todoItem, + user, + } +})() + +export default api diff --git a/mission002/eastjun/js/components/TodoInput.js b/mission002/eastjun/js/components/TodoInput.js new file mode 100644 index 0000000..09c560e --- /dev/null +++ b/mission002/eastjun/js/components/TodoInput.js @@ -0,0 +1,38 @@ +import validator from '../utils/validator.js' +import api from '../api/api.js' + +export default function TodoInput({ onAdd, setState }) { + const $todoInput = document.querySelector('#new-todo-title') + + const initEventListener = () => { + $todoInput.addEventListener('keydown', (event) => this.addTodoItem(event)) + } + + initEventListener() + + this.addTodoItem = async (event) => { + const $newTodoTarget = event.target + if (this.isValid(event, $newTodoTarget.value)) { + await this.onAdd($newTodoTarget) + } + } + + this.onAdd = async ($newTodoTarget) => { + try { + await api.todoItem.add($newTodoTarget.value) + const updateTodoItems = await api.todoItem.get() + setState(updateTodoItems) + this.initValue($newTodoTarget) + } catch (e) { + throw new Error(e) + } + } + + this.isValid = (event, newTodoContents) => { + return validator.isEnterKey(event.key) && validator.isString(newTodoContents) + } + + this.initValue = ($newTodoTarget) => { + $newTodoTarget.value = '' + } +} diff --git a/mission002/eastjun/js/components/TodoItem.js b/mission002/eastjun/js/components/TodoItem.js new file mode 100644 index 0000000..cbdeed8 --- /dev/null +++ b/mission002/eastjun/js/components/TodoItem.js @@ -0,0 +1,4 @@ +export default function TodoItem(contents) { + this.contents = contents + this.isCompleted = false +} diff --git a/mission002/eastjun/js/utils/templates.js b/mission002/eastjun/js/utils/templates.js new file mode 100644 index 0000000..11af372 --- /dev/null +++ b/mission002/eastjun/js/utils/templates.js @@ -0,0 +1,13 @@ +const todoItemTemplate = (todoItem) => ` +
  • +
    + + + +
    +
  • ` + + +export { + todoItemTemplate +} From 97ce9979252ce401f08f37f1475076e575b2e2d8 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 19:07:27 +0900 Subject: [PATCH 03/18] feat(utils): add utils --- mission002/eastjun/js/utils/utils.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 mission002/eastjun/js/utils/utils.js diff --git a/mission002/eastjun/js/utils/utils.js b/mission002/eastjun/js/utils/utils.js new file mode 100644 index 0000000..dd6a571 --- /dev/null +++ b/mission002/eastjun/js/utils/utils.js @@ -0,0 +1,20 @@ +const DEFAULT_USER = 'eastjun' + +const errorMessagesMap = { + NOT_NEW_INSTANCE: 'new를 이용해 생성한 인스턴스가 아닙니다.', + INVALID_DATA: '올바른 형태의 데이터가 아닙니다!', + IS_NOT_ARRAY: '올바른 배열 형태의 데이터가 아닙니다.' +} + +const todoItemStatusMap = { + ALL: 'all', + ACTIVE: 'active', + COMPLETED: 'completed' +} + +const keyboardMap = { + ENTER: 'Enter', + ESC: 'Escape' +} + +export { DEFAULT_USER, errorMessagesMap, todoItemStatusMap, keyboardMap } From dec8db46ebb8693ea120f4aba4e4edbcf440ada5 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 19:07:38 +0900 Subject: [PATCH 04/18] feat(validator): add validator --- mission002/eastjun/js/utils/validator.js | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 mission002/eastjun/js/utils/validator.js diff --git a/mission002/eastjun/js/utils/validator.js b/mission002/eastjun/js/utils/validator.js new file mode 100644 index 0000000..acb99c6 --- /dev/null +++ b/mission002/eastjun/js/utils/validator.js @@ -0,0 +1,27 @@ +import { errorMessagesMap, keyboardMap } from './utils.js' + +const validator = { + isValid(result, message) { + if (!result) throw new Error(message) + }, + isString(str) { + return typeof str === 'string' || str instanceof String + }, + isEmptyString(str) { + return (!str || 0 === str.length); + }, + isNewInstance(instance, originClass) { + this.validate(instance instanceof originClass, errorMessagesMap.NOT_NEW_INSTANCE) + }, + isNotEmptyArray(list) { + this.validate(Array.isArray(list) && !!list.length, errorMessagesMap.IS_NOT_ARRAY) + }, + isEnterKey(key) { + return key === keyboardMap.ENTER + }, + isEscKey(key) { + return key === keyboardMap.ESC + } +} + +export default validator From 73721d148d5bcc1630bb2fce67e93b341ec79650 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 19:07:54 +0900 Subject: [PATCH 05/18] feat(todoList): add todoList Component --- mission002/eastjun/js/components/TodoList.js | 80 ++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 mission002/eastjun/js/components/TodoList.js diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js new file mode 100644 index 0000000..504b158 --- /dev/null +++ b/mission002/eastjun/js/components/TodoList.js @@ -0,0 +1,80 @@ +import { todoItemTemplate } from '../utils/templates.js' +import validator from '../utils/validator.js' +import api from '../api/api.js' + +export default function TodoList({setState, onToggleItem, onRemoveItem}) { + this.$todoList = document.querySelector('#todo-list') + + const loadTodoItems = async () => { + try { + const todoItems = await api.todoItem.get() + setState(todoItems) + } catch (e) { + throw new Error(e) + } + } + + const initEventListener = () => { + this.$todoList.addEventListener('click', (event) => { + const { classList } = event.target + if (classList.contains('toggle')) onToggleItem(getIndex(event)) + if (classList.contains('destroy')) onRemoveItem(getIndex(event)) + }) + + this.$todoList.addEventListener('dblclick', (event) => { + const { classList } = event.target + if (classList.contains('label')) onFocusItem(event) + }) + + this.$todoList.addEventListener('keydown', (event) => { + if (event.target.classList.contains('edit')) onEdit(event) + }) + } + + this.init = () => { + loadTodoItems() + initEventListener() + } + + this.init() + + + this.setState = (updatedTodoItems) => { + this.todoItems = updatedTodoItems + this.render(this.todoItems) + } + + this.render = (items) => { + const template = items.map(todoItemTemplate) + this.$todoList.innerHTML = template.join('') + } + + const getIndex = (event) => { + return event.target.closest('li').dataset.id + } + + const onFocusItem = (event) => { + const $target = event.target.closest('li') + $target.classList.toggle('editing') + } + + const onEdit = (event) => { + const $target = event.target.closest('li') + const editValue = $target.querySelector('input.edit').value + + if (validator.isEnterKey(event.key)) { + isValidInputValue($target, editValue) + } + + if (validator.isEscKey(event.key)) { + $target.classList.toggle('editing') + } + } + + const isValidInputValue = ($target, inputValue) => { + if (validator.isString(inputValue) && !validator.isEmptyString(inputValue)){ + $target.querySelector('label').innerText = inputValue + $target.classList.toggle('editing') + } + } +} From 72872a9c4ba015b6e8e6c94fa7d6e9ec5ab42cd4 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 20:16:21 +0900 Subject: [PATCH 06/18] feat(remove): add remove api --- mission002/eastjun/js/App.js | 5 ----- mission002/eastjun/js/api/api.js | 13 ++++--------- mission002/eastjun/js/components/TodoList.js | 16 ++++++++++++++-- mission002/eastjun/js/utils/templates.js | 2 +- mission002/eastjun/js/utils/utils.js | 4 +--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 5a44093..83511e6 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -2,7 +2,6 @@ import TodoList from './components/TodoList.js' import TodoInput from './components/TodoInput.js' import TodoCount from './components/TodoCount.js' import TodoStatus from './components/TodoStatus.js' -import api from './api/api.js' function TodoApp() { this.todoItems = [] @@ -35,10 +34,6 @@ function TodoApp() { onToggleItem: (index) => { this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted this.setState(this.todoItems) - }, - onRemoveItem: (index) => { - this.todoItems.splice(index, 1) - this.setState(this.todoItems) } }) diff --git a/mission002/eastjun/js/api/api.js b/mission002/eastjun/js/api/api.js index 8ae0d3b..e59cecb 100644 --- a/mission002/eastjun/js/api/api.js +++ b/mission002/eastjun/js/api/api.js @@ -27,22 +27,17 @@ const METHOD = { } const api = (() => { - const request = (uri, config) => fetch(uri, config).then((data) => data.json()) + const request = (uri, config) => fetch(uri, config).then((response) => response.json()) const todoItem = { get: () => request(`${DOMAIN}${USERNAME}`), add: (data) => request(`${DOMAIN}${USERNAME}`, METHOD.POST(data)), - complete: (id) => request(`${DOMAIN}${USERNAME}/${id}/toggle`, METHOD.PUT), - remove: (id) => request(`${DOMAIN}${USERNAME}/${id}`, METHOD.DELETE), - } - - const user = { - get: () => request(`${DOMAIN}users`), + complete: (id) => request(`${DOMAIN}${USERNAME}/${id}/toggle`, METHOD.PUT()), + remove: (id) => request(`${DOMAIN}${USERNAME}/${id}`, METHOD.DELETE()), } return { - todoItem, - user, + todoItem } })() diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index 504b158..27dd9ba 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -2,7 +2,7 @@ import { todoItemTemplate } from '../utils/templates.js' import validator from '../utils/validator.js' import api from '../api/api.js' -export default function TodoList({setState, onToggleItem, onRemoveItem}) { +export default function TodoList({setState, onToggleItem}) { this.$todoList = document.querySelector('#todo-list') const loadTodoItems = async () => { @@ -18,7 +18,7 @@ export default function TodoList({setState, onToggleItem, onRemoveItem}) { this.$todoList.addEventListener('click', (event) => { const { classList } = event.target if (classList.contains('toggle')) onToggleItem(getIndex(event)) - if (classList.contains('destroy')) onRemoveItem(getIndex(event)) + if (classList.contains('destroy')) this.removeItem(event) }) this.$todoList.addEventListener('dblclick', (event) => { @@ -31,6 +31,18 @@ export default function TodoList({setState, onToggleItem, onRemoveItem}) { }) } + this.removeItem = async (event) => { + const $targetTodoItem = event.target.closest('li') + const itemId = $targetTodoItem.dataset.id + try { + await api.todoItem.remove(itemId) + const todoItems = await api.todoItem.get() + setState(todoItems) + } catch (e) { + throw new Error(e) + } + } + this.init = () => { loadTodoItems() initEventListener() diff --git a/mission002/eastjun/js/utils/templates.js b/mission002/eastjun/js/utils/templates.js index 11af372..ecdc105 100644 --- a/mission002/eastjun/js/utils/templates.js +++ b/mission002/eastjun/js/utils/templates.js @@ -3,7 +3,7 @@ const todoItemTemplate = (todoItem) => `
    - +
    ` diff --git a/mission002/eastjun/js/utils/utils.js b/mission002/eastjun/js/utils/utils.js index dd6a571..4444e0d 100644 --- a/mission002/eastjun/js/utils/utils.js +++ b/mission002/eastjun/js/utils/utils.js @@ -1,5 +1,3 @@ -const DEFAULT_USER = 'eastjun' - const errorMessagesMap = { NOT_NEW_INSTANCE: 'new를 이용해 생성한 인스턴스가 아닙니다.', INVALID_DATA: '올바른 형태의 데이터가 아닙니다!', @@ -17,4 +15,4 @@ const keyboardMap = { ESC: 'Escape' } -export { DEFAULT_USER, errorMessagesMap, todoItemStatusMap, keyboardMap } +export { errorMessagesMap, todoItemStatusMap, keyboardMap } From 52be4734eb54f224f2c80d855d4e0671e7d617a8 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 20:27:47 +0900 Subject: [PATCH 07/18] feat(toggle): add toggle api --- mission002/eastjun/js/App.js | 1 - mission002/eastjun/js/components/TodoList.js | 17 +++++++++++++++-- mission002/eastjun/js/utils/templates.js | 4 ++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 83511e6..08114ff 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -33,7 +33,6 @@ function TodoApp() { }, onToggleItem: (index) => { this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted - this.setState(this.todoItems) } }) diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index 27dd9ba..c97a1ff 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -17,7 +17,8 @@ export default function TodoList({setState, onToggleItem}) { const initEventListener = () => { this.$todoList.addEventListener('click', (event) => { const { classList } = event.target - if (classList.contains('toggle')) onToggleItem(getIndex(event)) + // if (classList.contains('toggle')) onToggleItem(getIndex(event)) + if (classList.contains('toggle')) this.toggleItem(event) if (classList.contains('destroy')) this.removeItem(event) }) @@ -31,6 +32,18 @@ export default function TodoList({setState, onToggleItem}) { }) } + this.toggleItem = async (event) => { + const $targetTodoItem = event.target.closest('li') + const itemId = $targetTodoItem.dataset.id + try { + await api.todoItem.complete(itemId) + $targetTodoItem.classList.toggle('completed') + onToggleItem(getIndex(event)) + } catch (e) { + throw new Error(e) + } + } + this.removeItem = async (event) => { const $targetTodoItem = event.target.closest('li') const itemId = $targetTodoItem.dataset.id @@ -62,7 +75,7 @@ export default function TodoList({setState, onToggleItem}) { } const getIndex = (event) => { - return event.target.closest('li').dataset.id + return event.target.closest('li').dataset.index } const onFocusItem = (event) => { diff --git a/mission002/eastjun/js/utils/templates.js b/mission002/eastjun/js/utils/templates.js index ecdc105..0788669 100644 --- a/mission002/eastjun/js/utils/templates.js +++ b/mission002/eastjun/js/utils/templates.js @@ -1,5 +1,5 @@ -const todoItemTemplate = (todoItem) => ` -
  • +const todoItemTemplate = (todoItem, index) => ` +
  • From 829eab0ae2f0d89a493c679616b9a6b0054783d6 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 21:45:26 +0900 Subject: [PATCH 08/18] feat(filter): add todoItem filter --- mission002/eastjun/js/App.js | 30 +++++++++++++++---- mission002/eastjun/js/components/TodoCount.js | 7 +++++ .../eastjun/js/components/TodoStatus.js | 24 +++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 mission002/eastjun/js/components/TodoCount.js create mode 100644 mission002/eastjun/js/components/TodoStatus.js diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 08114ff..d7f5919 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -2,14 +2,20 @@ import TodoList from './components/TodoList.js' import TodoInput from './components/TodoInput.js' import TodoCount from './components/TodoCount.js' import TodoStatus from './components/TodoStatus.js' +import { todoItemStatusMap } from './utils/utils.js' function TodoApp() { this.todoItems = [] this.setState = (updatedItems) => { this.todoItems = updatedItems - todoList.setState(this.todoItems) - todoCount.setState(this.todoItems) + todoList.render(this.todoItems) + todoCount.render(this.todoItems) + } + + this.render = (filteredItems) => { + todoList.render(filteredItems) + todoCount.render(filteredItems) } new TodoInput({ @@ -20,9 +26,23 @@ function TodoApp() { }) new TodoStatus({ - todoItems: this.todoItems, - filter: (filteredItems) => { - this.setState(filteredItems) + filter: (status) => { + switch (status) { + case todoItemStatusMap.ALL: { + this.render(this.todoItems) + break + } + case todoItemStatusMap.ACTIVE: { + const filteredItems = this.todoItems.filter(item => item.isCompleted === false) + this.render(filteredItems) + break + } + case todoItemStatusMap.COMPLETED: { + const filteredItems = this.todoItems.filter(item => item.isCompleted === true) + this.render(filteredItems) + break + } + } } }) diff --git a/mission002/eastjun/js/components/TodoCount.js b/mission002/eastjun/js/components/TodoCount.js new file mode 100644 index 0000000..7c82b6c --- /dev/null +++ b/mission002/eastjun/js/components/TodoCount.js @@ -0,0 +1,7 @@ +export default function TodoCount({ todoItems }) { + this.render = (todoItems) => { + document.querySelector('#total-count .count').innerHTML = todoItems.length + } + + this.render(todoItems) +} diff --git a/mission002/eastjun/js/components/TodoStatus.js b/mission002/eastjun/js/components/TodoStatus.js new file mode 100644 index 0000000..5c2ecdb --- /dev/null +++ b/mission002/eastjun/js/components/TodoStatus.js @@ -0,0 +1,24 @@ +export default function TodoStatus({ todoItems, filter }) { + this.$todoStatus = document.querySelector('#todo-status-tabs') + + const initEventListener = () => { + this.$todoStatus.addEventListener('click', (event) => { + const $target = event.target + filter($target.className) + toggleTab($target) + }) + } + + initEventListener() + + const toggleTab = ($target) => { + const $tabs = document.querySelectorAll('#todo-status-tabs li a') + $tabs.forEach(status => { + if (status.classList.contains('selected')) { + status.classList.remove('selected') + } + }) + $target.classList.add('selected') + } +} + From fd5090cd2e8aa6e2c5de02cf38aa49a7ccd001f7 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 21:57:25 +0900 Subject: [PATCH 09/18] refactor(TodoInput): refactor by creating TodoItem Object and adding --- mission002/eastjun/js/App.js | 15 ++++++--------- mission002/eastjun/js/api/api.js | 2 +- mission002/eastjun/js/components/TodoInput.js | 3 ++- mission002/eastjun/js/components/TodoItem.js | 2 +- mission002/eastjun/js/components/TodoList.js | 1 - 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index d7f5919..7393041 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -9,19 +9,17 @@ function TodoApp() { this.setState = (updatedItems) => { this.todoItems = updatedItems - todoList.render(this.todoItems) - todoCount.render(this.todoItems) + this.render(this.todoItems) } - this.render = (filteredItems) => { - todoList.render(filteredItems) - todoCount.render(filteredItems) + this.render = (items) => { + todoList.render(items) + todoCount.render(items) } new TodoInput({ setState: (todoItems) => { - this.todoItems = todoItems - this.setState(this.todoItems) + this.setState(todoItems) } }) @@ -48,8 +46,7 @@ function TodoApp() { const todoList = new TodoList({ setState: (todoItems) => { - this.todoItems = todoItems - this.setState(this.todoItems) + this.setState(todoItems) }, onToggleItem: (index) => { this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted diff --git a/mission002/eastjun/js/api/api.js b/mission002/eastjun/js/api/api.js index e59cecb..ee8046c 100644 --- a/mission002/eastjun/js/api/api.js +++ b/mission002/eastjun/js/api/api.js @@ -31,7 +31,7 @@ const api = (() => { const todoItem = { get: () => request(`${DOMAIN}${USERNAME}`), - add: (data) => request(`${DOMAIN}${USERNAME}`, METHOD.POST(data)), + add: (todoItem) => request(`${DOMAIN}${USERNAME}`, METHOD.POST(todoItem.content)), complete: (id) => request(`${DOMAIN}${USERNAME}/${id}/toggle`, METHOD.PUT()), remove: (id) => request(`${DOMAIN}${USERNAME}/${id}`, METHOD.DELETE()), } diff --git a/mission002/eastjun/js/components/TodoInput.js b/mission002/eastjun/js/components/TodoInput.js index 09c560e..b26d66b 100644 --- a/mission002/eastjun/js/components/TodoInput.js +++ b/mission002/eastjun/js/components/TodoInput.js @@ -1,5 +1,6 @@ import validator from '../utils/validator.js' import api from '../api/api.js' +import TodoItem from './TodoItem.js' export default function TodoInput({ onAdd, setState }) { const $todoInput = document.querySelector('#new-todo-title') @@ -19,7 +20,7 @@ export default function TodoInput({ onAdd, setState }) { this.onAdd = async ($newTodoTarget) => { try { - await api.todoItem.add($newTodoTarget.value) + await api.todoItem.add(new TodoItem($newTodoTarget.value)) const updateTodoItems = await api.todoItem.get() setState(updateTodoItems) this.initValue($newTodoTarget) diff --git a/mission002/eastjun/js/components/TodoItem.js b/mission002/eastjun/js/components/TodoItem.js index cbdeed8..963c843 100644 --- a/mission002/eastjun/js/components/TodoItem.js +++ b/mission002/eastjun/js/components/TodoItem.js @@ -1,4 +1,4 @@ export default function TodoItem(contents) { - this.contents = contents + this.content = contents this.isCompleted = false } diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index c97a1ff..0134fa2 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -17,7 +17,6 @@ export default function TodoList({setState, onToggleItem}) { const initEventListener = () => { this.$todoList.addEventListener('click', (event) => { const { classList } = event.target - // if (classList.contains('toggle')) onToggleItem(getIndex(event)) if (classList.contains('toggle')) this.toggleItem(event) if (classList.contains('destroy')) this.removeItem(event) }) From c916cd5566b5dfff4e49e00aa634ea47a76092b9 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 22:27:33 +0900 Subject: [PATCH 10/18] feat(localStorage): add feature to check network status and use localStorage. --- mission002/eastjun/js/App.js | 21 ++++++++++++++++++++ mission002/eastjun/js/components/TodoList.js | 17 +--------------- mission002/eastjun/js/store/localStorage.js | 16 +++++++++++++++ mission002/eastjun/js/utils/templates.js | 2 +- 4 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 mission002/eastjun/js/store/localStorage.js diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 7393041..3df1a20 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -3,13 +3,26 @@ import TodoInput from './components/TodoInput.js' import TodoCount from './components/TodoCount.js' import TodoStatus from './components/TodoStatus.js' import { todoItemStatusMap } from './utils/utils.js' +import storage from './store/localStorage.js' +import api from './api/api.js' function TodoApp() { this.todoItems = [] + this.isOnline = navigator.onLine + + const initNetworkEventListener = () => { + window.addEventListener('offline', () => this.isOnline = false) + window.addEventListener('online', () => this.isOnline = true) + } + + initNetworkEventListener() this.setState = (updatedItems) => { this.todoItems = updatedItems this.render(this.todoItems) + if (!this.isOnline) { + storage.set(this.todoItems) + } } this.render = (items) => { @@ -45,6 +58,14 @@ function TodoApp() { }) const todoList = new TodoList({ + loadTodoItems: async () => { + try { + const todoItems = this.isOnline ? await api.todoItem.get() : storage.get(this.todoItems) + this.setState(todoItems) + } catch (e) { + throw new Error(e) + } + }, setState: (todoItems) => { this.setState(todoItems) }, diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index 0134fa2..4105350 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -2,18 +2,9 @@ import { todoItemTemplate } from '../utils/templates.js' import validator from '../utils/validator.js' import api from '../api/api.js' -export default function TodoList({setState, onToggleItem}) { +export default function TodoList({loadTodoItems, setState, onToggleItem}) { this.$todoList = document.querySelector('#todo-list') - const loadTodoItems = async () => { - try { - const todoItems = await api.todoItem.get() - setState(todoItems) - } catch (e) { - throw new Error(e) - } - } - const initEventListener = () => { this.$todoList.addEventListener('click', (event) => { const { classList } = event.target @@ -62,12 +53,6 @@ export default function TodoList({setState, onToggleItem}) { this.init() - - this.setState = (updatedTodoItems) => { - this.todoItems = updatedTodoItems - this.render(this.todoItems) - } - this.render = (items) => { const template = items.map(todoItemTemplate) this.$todoList.innerHTML = template.join('') diff --git a/mission002/eastjun/js/store/localStorage.js b/mission002/eastjun/js/store/localStorage.js new file mode 100644 index 0000000..1a87929 --- /dev/null +++ b/mission002/eastjun/js/store/localStorage.js @@ -0,0 +1,16 @@ +const TODO_ITEM_KEY = 'todoItems' + +const storage = (() => { + const set = (todoItems) => { + localStorage.setItem(TODO_ITEM_KEY, JSON.stringify(todoItems)) + } + + const get = () => JSON.parse(localStorage.getItem(TODO_ITEM_KEY)) + + return { + get, + set, + } +})() + +export default storage diff --git a/mission002/eastjun/js/utils/templates.js b/mission002/eastjun/js/utils/templates.js index 0788669..0fd4778 100644 --- a/mission002/eastjun/js/utils/templates.js +++ b/mission002/eastjun/js/utils/templates.js @@ -1,5 +1,5 @@ const todoItemTemplate = (todoItem, index) => ` -
  • +
  • From 61ba6c40dabb3594a415694800a734907794bb77 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 22:44:00 +0900 Subject: [PATCH 11/18] refactor(template): add edit input tag --- mission002/eastjun/js/utils/templates.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mission002/eastjun/js/utils/templates.js b/mission002/eastjun/js/utils/templates.js index 0fd4778..1a2f624 100644 --- a/mission002/eastjun/js/utils/templates.js +++ b/mission002/eastjun/js/utils/templates.js @@ -5,6 +5,7 @@ const todoItemTemplate = (todoItem, index) => `
    +
  • ` From de33444d7629de3d6331c940b6def991009164e6 Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 22:44:24 +0900 Subject: [PATCH 12/18] refactor(classList): refactor classList variables naming --- mission002/eastjun/js/components/TodoList.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index 4105350..710a11b 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -7,22 +7,23 @@ export default function TodoList({loadTodoItems, setState, onToggleItem}) { const initEventListener = () => { this.$todoList.addEventListener('click', (event) => { - const { classList } = event.target - if (classList.contains('toggle')) this.toggleItem(event) - if (classList.contains('destroy')) this.removeItem(event) + const classList = event.target.classList + if (classList.contains('toggle')) onToggleItem(event) + if (classList.contains('destroy')) onRemoveItem(event) }) this.$todoList.addEventListener('dblclick', (event) => { - const { classList } = event.target + const classList = event.target.classList if (classList.contains('label')) onFocusItem(event) }) this.$todoList.addEventListener('keydown', (event) => { - if (event.target.classList.contains('edit')) onEdit(event) + const classList = event.target.classList + if (classList.contains('edit')) onEdit(event) }) } - this.toggleItem = async (event) => { + const onToggleItem = async (event) => { const $targetTodoItem = event.target.closest('li') const itemId = $targetTodoItem.dataset.id try { @@ -34,7 +35,7 @@ export default function TodoList({loadTodoItems, setState, onToggleItem}) { } } - this.removeItem = async (event) => { + const onRemoveItem = async (event) => { const $targetTodoItem = event.target.closest('li') const itemId = $targetTodoItem.dataset.id try { @@ -72,11 +73,11 @@ export default function TodoList({loadTodoItems, setState, onToggleItem}) { const editValue = $target.querySelector('input.edit').value if (validator.isEnterKey(event.key)) { - isValidInputValue($target, editValue) + return isValidInputValue($target, editValue) } if (validator.isEscKey(event.key)) { - $target.classList.toggle('editing') + return $target.classList.toggle('editing') } } From e9c76417e5f4eb4cee918a77c7d25393323d988d Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 22:45:12 +0900 Subject: [PATCH 13/18] refactor(toogleItem): rename toggleItem function --- mission002/eastjun/js/App.js | 2 +- mission002/eastjun/js/components/TodoList.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 3df1a20..1db5d22 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -69,7 +69,7 @@ function TodoApp() { setState: (todoItems) => { this.setState(todoItems) }, - onToggleItem: (index) => { + toggleItem: (index) => { this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted } }) diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index 710a11b..7dfce10 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -29,7 +29,7 @@ export default function TodoList({loadTodoItems, setState, onToggleItem}) { try { await api.todoItem.complete(itemId) $targetTodoItem.classList.toggle('completed') - onToggleItem(getIndex(event)) + toggleItem(getIndex(event)) } catch (e) { throw new Error(e) } From 46cfcc68ba73279298a03751177491890faeb27d Mon Sep 17 00:00:00 2001 From: eastjun Date: Wed, 25 Dec 2019 22:46:20 +0900 Subject: [PATCH 14/18] refactor(toggleItem): rename parameter in TodoList --- mission002/eastjun/js/components/TodoList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index 7dfce10..27ee96d 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -2,7 +2,7 @@ import { todoItemTemplate } from '../utils/templates.js' import validator from '../utils/validator.js' import api from '../api/api.js' -export default function TodoList({loadTodoItems, setState, onToggleItem}) { +export default function TodoList({loadTodoItems, setState, toggleItem}) { this.$todoList = document.querySelector('#todo-list') const initEventListener = () => { From 481423b3499e5e3a351b6aa61904974d6238bdd4 Mon Sep 17 00:00:00 2001 From: eastjun Date: Mon, 30 Dec 2019 02:35:20 +0900 Subject: [PATCH 15/18] feat(offlineMode): add offline mode alert logic --- mission002/eastjun/css/style.css | 9 ++++ mission002/eastjun/index.html | 3 ++ mission002/eastjun/js/App.js | 49 +++++++++++++++---- mission002/eastjun/js/components/TodoCount.js | 2 +- mission002/eastjun/js/components/TodoInput.js | 17 +++++-- mission002/eastjun/js/components/TodoList.js | 28 +++++++++-- mission002/eastjun/js/utils/templates.js | 3 +- mission002/eastjun/js/utils/utils.js | 12 ++--- mission002/eastjun/js/utils/validator.js | 15 ++---- 9 files changed, 96 insertions(+), 42 deletions(-) diff --git a/mission002/eastjun/css/style.css b/mission002/eastjun/css/style.css index 14a905e..d0185ba 100644 --- a/mission002/eastjun/css/style.css +++ b/mission002/eastjun/css/style.css @@ -66,6 +66,15 @@ body { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; } +.todoapp .offline { + position: absolute; + top: -45px; + width: 100%; + text-align: center; + color: red; + +} + .new-todo, .edit { position: relative; diff --git a/mission002/eastjun/index.html b/mission002/eastjun/index.html index f996bd9..8590543 100644 --- a/mission002/eastjun/index.html +++ b/mission002/eastjun/index.html @@ -10,6 +10,9 @@

    TODOS

    +
    + +
    diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 1db5d22..14a6c89 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -11,17 +11,19 @@ function TodoApp() { this.isOnline = navigator.onLine const initNetworkEventListener = () => { - window.addEventListener('offline', () => this.isOnline = false) - window.addEventListener('online', () => this.isOnline = true) + window.addEventListener('offline', () => setIsOnline()) + window.addEventListener('online', () => setIsOnline()) } initNetworkEventListener() - this.setState = (updatedItems) => { - this.todoItems = updatedItems - this.render(this.todoItems) + const setIsOnline = () => { + this.isOnline = navigator.onLine + const $offlineAlert = document.querySelector('.alert-container .offline') if (!this.isOnline) { - storage.set(this.todoItems) + $offlineAlert.classList.remove('hidden') + } else { + $offlineAlert.classList.add('hidden') } } @@ -30,9 +32,33 @@ function TodoApp() { todoCount.render(items) } + this.setState = (updatedItems) => { + this.todoItems = updatedItems + storage.set(this.todoItems) + this.render(this.todoItems) + } + + const offlineMode = () => { + const initTodoList = () => { + const $offlineAlert = document.querySelector('.alert-container .offline') + $offlineAlert.classList.remove('hidden') + this.todoItems = storage.get() + if (this.todoItems) { + this.render(this.todoItems) + } + } + + return { + initTodoList, + } + } + new TodoInput({ setState: (todoItems) => { this.setState(todoItems) + }, + addTodoItem: (todoItem) => { + offlineMode().addTodoItem(todoItem) } }) @@ -60,7 +86,7 @@ function TodoApp() { const todoList = new TodoList({ loadTodoItems: async () => { try { - const todoItems = this.isOnline ? await api.todoItem.get() : storage.get(this.todoItems) + const todoItems = await api.todoItem.get() this.setState(todoItems) } catch (e) { throw new Error(e) @@ -71,12 +97,17 @@ function TodoApp() { }, toggleItem: (index) => { this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted - } + }, }) const todoCount = new TodoCount({ - todoItems: this.todoItems + todoItems: this.todoItems, }) + + if (!this.isOnline) { + offlineMode().initTodoList() + } + } new TodoApp() diff --git a/mission002/eastjun/js/components/TodoCount.js b/mission002/eastjun/js/components/TodoCount.js index 7c82b6c..bdfc70b 100644 --- a/mission002/eastjun/js/components/TodoCount.js +++ b/mission002/eastjun/js/components/TodoCount.js @@ -1,6 +1,6 @@ export default function TodoCount({ todoItems }) { this.render = (todoItems) => { - document.querySelector('#total-count .count').innerHTML = todoItems.length + document.querySelector('#total-count .count').innerHTML = todoItems ? todoItems.length : 0 } this.render(todoItems) diff --git a/mission002/eastjun/js/components/TodoInput.js b/mission002/eastjun/js/components/TodoInput.js index b26d66b..b47e217 100644 --- a/mission002/eastjun/js/components/TodoInput.js +++ b/mission002/eastjun/js/components/TodoInput.js @@ -2,7 +2,7 @@ import validator from '../utils/validator.js' import api from '../api/api.js' import TodoItem from './TodoItem.js' -export default function TodoInput({ onAdd, setState }) { +export default function TodoInput({ addTodoItem, setState }) { const $todoInput = document.querySelector('#new-todo-title') const initEventListener = () => { @@ -13,24 +13,31 @@ export default function TodoInput({ onAdd, setState }) { this.addTodoItem = async (event) => { const $newTodoTarget = event.target - if (this.isValid(event, $newTodoTarget.value)) { - await this.onAdd($newTodoTarget) + + if (!this.isValid(event, $newTodoTarget.value)) { + return } + + this.onAdd($newTodoTarget) } this.onAdd = async ($newTodoTarget) => { + if (!navigator.onLine) { + return + } + try { await api.todoItem.add(new TodoItem($newTodoTarget.value)) + this.initValue($newTodoTarget) const updateTodoItems = await api.todoItem.get() setState(updateTodoItems) - this.initValue($newTodoTarget) } catch (e) { throw new Error(e) } } this.isValid = (event, newTodoContents) => { - return validator.isEnterKey(event.key) && validator.isString(newTodoContents) + return validator.isEnterKey(event.key) && !validator.isEmptyString(newTodoContents) } this.initValue = ($newTodoTarget) => { diff --git a/mission002/eastjun/js/components/TodoList.js b/mission002/eastjun/js/components/TodoList.js index 27ee96d..9083480 100644 --- a/mission002/eastjun/js/components/TodoList.js +++ b/mission002/eastjun/js/components/TodoList.js @@ -2,7 +2,7 @@ import { todoItemTemplate } from '../utils/templates.js' import validator from '../utils/validator.js' import api from '../api/api.js' -export default function TodoList({loadTodoItems, setState, toggleItem}) { +export default function TodoList({ loadTodoItems, setState, toggleItem }) { this.$todoList = document.querySelector('#todo-list') const initEventListener = () => { @@ -24,11 +24,21 @@ export default function TodoList({loadTodoItems, setState, toggleItem}) { } const onToggleItem = async (event) => { + if (!navigator.onLine) { + return + } + const $targetTodoItem = event.target.closest('li') - const itemId = $targetTodoItem.dataset.id + $targetTodoItem.classList.toggle('completed') + + if (!navigator.onLine) { + toggleItem(getIndex(event)) + return + } + try { + const itemId = $targetTodoItem.dataset.id await api.todoItem.complete(itemId) - $targetTodoItem.classList.toggle('completed') toggleItem(getIndex(event)) } catch (e) { throw new Error(e) @@ -36,6 +46,10 @@ export default function TodoList({loadTodoItems, setState, toggleItem}) { } const onRemoveItem = async (event) => { + if (!navigator.onLine) { + return + } + const $targetTodoItem = event.target.closest('li') const itemId = $targetTodoItem.dataset.id try { @@ -48,7 +62,9 @@ export default function TodoList({loadTodoItems, setState, toggleItem}) { } this.init = () => { - loadTodoItems() + if (navigator.onLine) { + loadTodoItems() + } initEventListener() } @@ -69,6 +85,10 @@ export default function TodoList({loadTodoItems, setState, toggleItem}) { } const onEdit = (event) => { + if (!navigator.onLine) { + return + } + const $target = event.target.closest('li') const editValue = $target.querySelector('input.edit').value diff --git a/mission002/eastjun/js/utils/templates.js b/mission002/eastjun/js/utils/templates.js index 1a2f624..4362d4c 100644 --- a/mission002/eastjun/js/utils/templates.js +++ b/mission002/eastjun/js/utils/templates.js @@ -8,7 +8,6 @@ const todoItemTemplate = (todoItem, index) => ` ` - export { - todoItemTemplate + todoItemTemplate, } diff --git a/mission002/eastjun/js/utils/utils.js b/mission002/eastjun/js/utils/utils.js index 4444e0d..d97eab5 100644 --- a/mission002/eastjun/js/utils/utils.js +++ b/mission002/eastjun/js/utils/utils.js @@ -1,18 +1,12 @@ -const errorMessagesMap = { - NOT_NEW_INSTANCE: 'new를 이용해 생성한 인스턴스가 아닙니다.', - INVALID_DATA: '올바른 형태의 데이터가 아닙니다!', - IS_NOT_ARRAY: '올바른 배열 형태의 데이터가 아닙니다.' -} - const todoItemStatusMap = { ALL: 'all', ACTIVE: 'active', - COMPLETED: 'completed' + COMPLETED: 'completed', } const keyboardMap = { ENTER: 'Enter', - ESC: 'Escape' + ESC: 'Escape', } -export { errorMessagesMap, todoItemStatusMap, keyboardMap } +export { todoItemStatusMap, keyboardMap } diff --git a/mission002/eastjun/js/utils/validator.js b/mission002/eastjun/js/utils/validator.js index acb99c6..1d5ece0 100644 --- a/mission002/eastjun/js/utils/validator.js +++ b/mission002/eastjun/js/utils/validator.js @@ -1,27 +1,18 @@ -import { errorMessagesMap, keyboardMap } from './utils.js' +import { keyboardMap } from './utils.js' const validator = { - isValid(result, message) { - if (!result) throw new Error(message) - }, isString(str) { return typeof str === 'string' || str instanceof String }, isEmptyString(str) { - return (!str || 0 === str.length); - }, - isNewInstance(instance, originClass) { - this.validate(instance instanceof originClass, errorMessagesMap.NOT_NEW_INSTANCE) - }, - isNotEmptyArray(list) { - this.validate(Array.isArray(list) && !!list.length, errorMessagesMap.IS_NOT_ARRAY) + return (!str || 0 === str.length) }, isEnterKey(key) { return key === keyboardMap.ENTER }, isEscKey(key) { return key === keyboardMap.ESC - } + }, } export default validator From 2ca61ba767784ef4c84375f6b05cdb9677d00f18 Mon Sep 17 00:00:00 2001 From: eastjun Date: Tue, 31 Dec 2019 09:31:23 +0900 Subject: [PATCH 16/18] refactor(offlineMode): refactor offlineMode's nested function --- mission002/eastjun/js/App.js | 23 ++++++------------- mission002/eastjun/js/components/TodoInput.js | 2 +- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 14a6c89..30f5635 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -38,27 +38,18 @@ function TodoApp() { this.render(this.todoItems) } - const offlineMode = () => { - const initTodoList = () => { - const $offlineAlert = document.querySelector('.alert-container .offline') - $offlineAlert.classList.remove('hidden') - this.todoItems = storage.get() - if (this.todoItems) { - this.render(this.todoItems) - } - } - - return { - initTodoList, + this.initOfflineTodoList = () => { + const $offlineAlert = document.querySelector('.alert-container .offline') + $offlineAlert.classList.remove('hidden') + this.todoItems = storage.get() + if (this.todoItems) { + this.render(this.todoItems) } } new TodoInput({ setState: (todoItems) => { this.setState(todoItems) - }, - addTodoItem: (todoItem) => { - offlineMode().addTodoItem(todoItem) } }) @@ -105,7 +96,7 @@ function TodoApp() { }) if (!this.isOnline) { - offlineMode().initTodoList() + this.initOfflineTodoList() } } diff --git a/mission002/eastjun/js/components/TodoInput.js b/mission002/eastjun/js/components/TodoInput.js index b47e217..2f33b9f 100644 --- a/mission002/eastjun/js/components/TodoInput.js +++ b/mission002/eastjun/js/components/TodoInput.js @@ -2,7 +2,7 @@ import validator from '../utils/validator.js' import api from '../api/api.js' import TodoItem from './TodoItem.js' -export default function TodoInput({ addTodoItem, setState }) { +export default function TodoInput({ setState }) { const $todoInput = document.querySelector('#new-todo-title') const initEventListener = () => { From cd706053ed1d64833ebcf6a56f439471c87291c9 Mon Sep 17 00:00:00 2001 From: eastjun Date: Tue, 31 Dec 2019 23:13:23 +0900 Subject: [PATCH 17/18] refactor(setIsOnline): refactor if else to conditional operator --- mission002/eastjun/js/App.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index 30f5635..be00ef8 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -19,12 +19,8 @@ function TodoApp() { const setIsOnline = () => { this.isOnline = navigator.onLine - const $offlineAlert = document.querySelector('.alert-container .offline') - if (!this.isOnline) { - $offlineAlert.classList.remove('hidden') - } else { - $offlineAlert.classList.add('hidden') - } + const offlineAlertClassList = document.querySelector('.alert-container .offline').classList + this.isOnline ? offlineAlertClassList.add('hidden') : offlineAlertClassList.remove('hidden') } this.render = (items) => { From 390a839ca05feafa659612abefbf78db16dbe850 Mon Sep 17 00:00:00 2001 From: eastjun Date: Sun, 9 Feb 2020 12:34:37 +0900 Subject: [PATCH 18/18] refactor(convention): refactor indent --- mission002/eastjun/js/App.js | 149 +++++++++++++++++------------------ 1 file changed, 74 insertions(+), 75 deletions(-) diff --git a/mission002/eastjun/js/App.js b/mission002/eastjun/js/App.js index be00ef8..b1b86f0 100644 --- a/mission002/eastjun/js/App.js +++ b/mission002/eastjun/js/App.js @@ -6,95 +6,94 @@ import { todoItemStatusMap } from './utils/utils.js' import storage from './store/localStorage.js' import api from './api/api.js' -function TodoApp() { - this.todoItems = [] - this.isOnline = navigator.onLine +function TodoApp () { + this.todoItems = [] + this.isOnline = navigator.onLine - const initNetworkEventListener = () => { - window.addEventListener('offline', () => setIsOnline()) - window.addEventListener('online', () => setIsOnline()) - } + const initNetworkEventListener = () => { + window.addEventListener('offline', () => setIsOnline()) + window.addEventListener('online', () => setIsOnline()) + } - initNetworkEventListener() + initNetworkEventListener() - const setIsOnline = () => { - this.isOnline = navigator.onLine - const offlineAlertClassList = document.querySelector('.alert-container .offline').classList - this.isOnline ? offlineAlertClassList.add('hidden') : offlineAlertClassList.remove('hidden') - } + const setIsOnline = () => { + this.isOnline = navigator.onLine + const offlineAlertClassList = document.querySelector('.alert-container .offline').classList + this.isOnline ? offlineAlertClassList.add('hidden') : offlineAlertClassList.remove('hidden') + } - this.render = (items) => { - todoList.render(items) - todoCount.render(items) - } + this.render = (items) => { + todoList.render(items) + todoCount.render(items) + } + + this.setState = (updatedItems) => { + this.todoItems = updatedItems + storage.set(this.todoItems) + this.render(this.todoItems) + } - this.setState = (updatedItems) => { - this.todoItems = updatedItems - storage.set(this.todoItems) - this.render(this.todoItems) + this.initOfflineTodoList = () => { + const $offlineAlert = document.querySelector('.alert-container .offline') + $offlineAlert.classList.remove('hidden') + this.todoItems = storage.get() + if (this.todoItems) { + this.render(this.todoItems) } + } - this.initOfflineTodoList = () => { - const $offlineAlert = document.querySelector('.alert-container .offline') - $offlineAlert.classList.remove('hidden') - this.todoItems = storage.get() - if (this.todoItems) { - this.render(this.todoItems) - } + new TodoInput({ + setState: (todoItems) => { + this.setState(todoItems) } + }) - new TodoInput({ - setState: (todoItems) => { - this.setState(todoItems) + new TodoStatus({ + filter: (status) => { + switch (status) { + case todoItemStatusMap.ALL: { + this.render(this.todoItems) + break } - }) - - new TodoStatus({ - filter: (status) => { - switch (status) { - case todoItemStatusMap.ALL: { - this.render(this.todoItems) - break - } - case todoItemStatusMap.ACTIVE: { - const filteredItems = this.todoItems.filter(item => item.isCompleted === false) - this.render(filteredItems) - break - } - case todoItemStatusMap.COMPLETED: { - const filteredItems = this.todoItems.filter(item => item.isCompleted === true) - this.render(filteredItems) - break - } - } + case todoItemStatusMap.ACTIVE: { + const filteredItems = this.todoItems.filter(item => item.isCompleted === false) + this.render(filteredItems) + break } - }) - - const todoList = new TodoList({ - loadTodoItems: async () => { - try { - const todoItems = await api.todoItem.get() - this.setState(todoItems) - } catch (e) { - throw new Error(e) - } - }, - setState: (todoItems) => { - this.setState(todoItems) - }, - toggleItem: (index) => { - this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted - }, - }) - - const todoCount = new TodoCount({ - todoItems: this.todoItems, - }) + case todoItemStatusMap.COMPLETED: { + const filteredItems = this.todoItems.filter(item => item.isCompleted === true) + this.render(filteredItems) + break + } + } + } + }) - if (!this.isOnline) { - this.initOfflineTodoList() + const todoList = new TodoList({ + loadTodoItems: async () => { + try { + const todoItems = await api.todoItem.get() + this.setState(todoItems) + } catch (e) { + throw new Error(e) + } + }, + setState: (todoItems) => { + this.setState(todoItems) + }, + toggleItem: (index) => { + this.todoItems[index].isCompleted = !this.todoItems[index].isCompleted } + }) + + const todoCount = new TodoCount({ + todoItems: this.todoItems + }) + if (!this.isOnline) { + this.initOfflineTodoList() + } } new TodoApp()