diff --git a/index.html b/index.html index 13a02fdb..89702ac4 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,32 @@

TODOS

/>
- +
0
+ diff --git a/src/js/component/TodoApp.js b/src/js/component/TodoApp.js new file mode 100644 index 00000000..ee41bee2 --- /dev/null +++ b/src/js/component/TodoApp.js @@ -0,0 +1,67 @@ +import TodoInput from '../component/TodoInput.js' +import TodoList from '../component/TodoList.js' +import TodoCount from '../component/TodoCount.js' + +import { storage } from '../util/Storage.js' + +function TodoApp () { + this.TODOS_KEY = "todos" + this.todos = (localStorage.getItem(this.TODOS_KEY)) ? storage.get(this.TODOS_KEY) : [] + + this.handleAddTodo = (todoItem) => { + const newTodos = this.todos.slice() + newTodos.push(todoItem) + this.setState(newTodos) + storage.set(this.TODOS_KEY, this.todos) + } + + this.handleToggleTodo = (target) => { + const liItem = target.closest("li") + this.todos.map(item => { + if(item.id == target.id) { + if(item.status == "completed") { + item.status = "active" + liItem.classList.remove("completed") + } else if(item.status == "active") { + item.status = "completed" + liItem.classList.add("completed") + } + } + }) + storage.set(this.TODOS_KEY, this.todos) + } + + this.handleRemoveTodo = (target) => { + this.todos = this.todos.filter(item => { + if(item.id !== target.id) { + return item + } + }) + this.setState(this.todos) + storage.set(this.TODOS_KEY, this.todos) + } + + this.render = () => { + this.todoInput = new TodoInput({ + onAddTodo: this.handleAddTodo.bind(this) + }) + this.todoCount = new TodoCount({ + todos: this.todos + }) + this.todoList = new TodoList({ + todos: this.todos, + onToggleTodo: this.handleToggleTodo.bind(this), + onRemoveTodo: this.handleRemoveTodo.bind(this) + }) + } + + this.setState = (nextState) => { + this.todos = nextState + this.todoList.setState(nextState) + this.todoCount.setState(nextState) + } + + this.render() +} + +export default TodoApp \ No newline at end of file diff --git a/src/js/component/TodoCount.js b/src/js/component/TodoCount.js new file mode 100644 index 00000000..663a675d --- /dev/null +++ b/src/js/component/TodoCount.js @@ -0,0 +1,26 @@ +import $ from '../util/QuerySelector.js' + +function TodoCount({ + todos +}) { + this.todos = todos + this.todoCountGroup = $('.todo-count') + this.todoCount = this.todoCountGroup.querySelector("strong") + + this.handleCountTodo = () => { + this.todoCount.textContent = this.todos.length + } + + this.render = () => { + this.handleCountTodo() + } + + this.setState = (nextState) => { + this.todos = nextState + this.render() + } + + this.render() +} + +export default TodoCount \ No newline at end of file diff --git a/src/js/component/TodoInput.js b/src/js/component/TodoInput.js new file mode 100644 index 00000000..abba8582 --- /dev/null +++ b/src/js/component/TodoInput.js @@ -0,0 +1,30 @@ +import $ from '../util/QuerySelector.js' + +function TodoInput({ + onAddTodo +}) { + this.onAddTodo = onAddTodo + + this.newTodo = $('.new-todo') + + this.handleAddTodo = () => { + this.newTodo.addEventListener("keyup", (e) => { + if(e.keyCode === 13) { + const todoItem = { + id: String(Date.now()), + todo : this.newTodo.value, + status: "active" + } + this.onAddTodo(todoItem) + } + }) + } + + this.render = () => { + this.handleAddTodo() + } + + this.render() +} + +export default TodoInput \ No newline at end of file diff --git a/src/js/component/TodoList.js b/src/js/component/TodoList.js new file mode 100644 index 00000000..680b428e --- /dev/null +++ b/src/js/component/TodoList.js @@ -0,0 +1,105 @@ +import $ from '../util/QuerySelector.js' +import { + STATUS_ALL, + STATUS_ACTIVE, + STATUS_COMPLETED +} from '../util/Constant.js' + +function TodoList ({ + todos, + onToggleTodo, + onRemoveTodo +}) { + this.todoList = $('.todo-list') + this.filters = $('.filters') + + this.onToggleTodo = onToggleTodo + this.onRemoveTodo = onRemoveTodo + this.todos = todos + + this.todoTemplate = (item) => { + return `
  • +
    + + + +
    +
  • ` + } + + this.handleMapAllTodo = () => { + this.todos.map(item => { + this.todoList.insertAdjacentHTML('beforeend', this.todoTemplate(item)) + }) + } + + this.handleMapActiveTodo = () => { + const todos = this.todos.filter(item => item.status === 'active') + todos.map(item => { + this.todoList.insertAdjacentHTML('beforeend', this.todoTemplate(item)) + }) + } + + this.handleMapCompletedTodo = () => { + const todos = this.todos.filter(item => item.status === 'completed') + todos.map(item => { + this.todoList.insertAdjacentHTML('beforeend', this.todoTemplate(item)) + }) + } + + this.mapTodos = (option = STATUS_ALL) => { + this.todoList.innerHTML = ''; + + switch(option) { + case STATUS_ALL : + this.handleMapAllTodo() + break; + case STATUS_ACTIVE : + this.handleMapActiveTodo() + break; + case STATUS_COMPLETED : + this.handleMapCompletedTodo() + break; + } + } + + this.handleBindEvents = () => { + this.filters.addEventListener("click", e => { + if(e.target.nodeName === 'A') { + e.target.closest('ul') + .querySelectorAll('a') + .forEach((target) => target.classList.remove('selected')) + e.target.classList.add('selected') + } + if(e.target.classList.contains("active")) { + this.mapTodos(STATUS_ACTIVE) + } else if(e.target.classList.contains("completed")) { + this.mapTodos(STATUS_COMPLETED) + } else if(e.target.classList.contains("all")) { + this.mapTodos(STATUS_ALL) + } + }) + + this.todoList.addEventListener("click", e => { + if(e.target.classList.contains("destroy")) { + this.onRemoveTodo(e.target) + } else if(e.target.classList.contains("toggle")) { + this.onToggleTodo(e.target) + } + }) + } + + this.render = () => { + this.handleBindEvents() + this.mapTodos() + } + + this.setState = (nextState) => { + this.todos = nextState + this.render() + } + + this.render() +} + +export default TodoList \ No newline at end of file diff --git a/src/js/index.js b/src/js/index.js new file mode 100644 index 00000000..bada07f7 --- /dev/null +++ b/src/js/index.js @@ -0,0 +1,3 @@ +import TodoApp from "./component/TodoApp.js"; + +const todo = new TodoApp() \ No newline at end of file diff --git a/src/js/util/Constant.js b/src/js/util/Constant.js new file mode 100644 index 00000000..4548d771 --- /dev/null +++ b/src/js/util/Constant.js @@ -0,0 +1,3 @@ +export const STATUS_ALL = "is-all" +export const STATUS_ACTIVE = "is-active" +export const STATUS_COMPLETED = "is-completed" \ No newline at end of file diff --git a/src/js/util/QuerySelector.js b/src/js/util/QuerySelector.js new file mode 100644 index 00000000..588bed07 --- /dev/null +++ b/src/js/util/QuerySelector.js @@ -0,0 +1,3 @@ +const $ = (selector) => document.querySelector(selector) + +export default $ \ No newline at end of file diff --git a/src/js/util/Storage.js b/src/js/util/Storage.js new file mode 100644 index 00000000..beb7edb6 --- /dev/null +++ b/src/js/util/Storage.js @@ -0,0 +1,12 @@ +export const storage = { + get: (TODOS_DATA) => { + try { + return JSON.parse(localStorage.getItem(TODOS_DATA)) + } catch(error) { + console.log(error) + } + }, + set: (key, value) => { + localStorage.setItem(key, JSON.stringify(value)) + } +}