This commit is contained in:
2026-02-06 13:15:23 +03:00
parent 9a2e09864b
commit 188b6ccc0e
2 changed files with 156 additions and 25 deletions

View File

@@ -9,7 +9,7 @@
</head> </head>
<body> <body>
<div class="todo data-js-todo"> <div class="todo" data-js-todo>
<h1 class="todo_title">To Do List</h1> <h1 class="todo_title">To Do List</h1>
<form class="todo_form" data-js-todo-new-task-form> <form class="todo_form" data-js-todo-new-task-form>
<div class="todo_field field"> <div class="todo_field field">
@@ -36,30 +36,7 @@
Delete All Delete All
</button> </button>
</div> </div>
<ul class="todo_list" data-js-todo-list> <ul class="todo_list" data-js-todo-list></ul>
<li class="todo_item todo-item" data-js-todo-item>
<input class="todo_item_checkbox" type="checkbox" id="todo-1" data-js-todo-item-checkbox>
<label class="todo-item_label" for="todo-1" data-js-todo-item-label>Todo 1</label>
<button class="todo_item_delete-button" type="button" aria-label="Delete" title="Delete" data-js-todo-item-delete-button>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 5L5 15M5 5L15 15" stroke="#757575" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
<li class="todo_item todo-item" data-js-todo-item>
<input class="todo_item_checkbox" type="checkbox" id="todo-1" data-js-todo-item-checkbox>
<label class="todo-item_label" for="todo-1" data-js-todo-item-label>Todo 1</label>
<button class="todo_item_delete-button" type="button" aria-label="Delete" title="Delete" data-js-todo-item-delete-button>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 5L5 15M5 5L15 15" stroke="#757575" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
</ul>
<div class="todo_empty-message" data-js-todo-empty-message> <div class="todo_empty-message" data-js-todo-empty-message>
</div> </div>

View File

@@ -0,0 +1,154 @@
class Todo {
selectors = {
root: '[data-js-todo]',
newTaskForm: '[data-js-todo-new-task-form]',
newTaskInput: '[data-js-todo-new-task-input]',
searchTaskForm: '[data-js-todo-search-task-form]',
searchTaskInput: '[data-js-todo-search-task-input]',
totalTasks: '[data-js-todo-total-tasks]',
deleteAllButton: '[data-js-todo-delete-all-button]',
list: '[data-js-todo-list]',
item: '[data-js-todo-item]',
itemCheckbox: '[data-js-todo-item-checkbox]',
itemLabel: '[data-js-todo-item-label]',
itemDeleteButton: '[data-js-todo-item-delete-button]',
emptyMessage: '[data-js-todo-empty-message]',
}
stateClasses = {
isVisible: 'is-visible',
isDisappearing: 'is-disappearing',
}
localStorageKey = 'todo-items'
constructor() {
this.rootElement = document.querySelector(this.selectors.root)
this.newTaskFormElement = this.rootElement.querySelector.querySelector(this.selectors.newTaskForm)
this.newTaskInputElement = this.rootElement.querySelector.querySelector(this.selectors.newTaskInput)
this.searchTaskFormElement = this.rootElement.querySelector.querySelector(this.selectors.searchTaskForm)
this.searchTaskInputElement = this.rootElement.querySelector.querySelector(this.selectors.searchTaskInput)
this.totalTasksElement = this.rootElement.querySelector.querySelector(this.selectors.totalTasks)
this.deleteAllButtonElement = this.rootElement.querySelector.querySelector(this.selectors.deleteAllButton)
this.listElement = this.rootElement.querySelector.querySelector(this.selectors.list)
this.emptyMessageElement = this.rootElement.querySelector.querySelector(this.selectors.emptyMessage)
this.state = {
items: this.getItemsFromLocalStorage(),
filteredItems: null,
searchQuery: "",
}
this.render()
}
getItemsFromLocalStorage() {
const rawData = localStorage.getItem(this.localStorageKey)
if (!rawData) {
return []
}
try {
const parsedData = JSON.parse(rawData)
return Array.isArray(parsedData) ? parsedData : []
} catch {
console.error("Todo items parse error")
return []
}
}
saveItemsToLocalStorage() {
localStorage.setItem(
this.localStorageKey,
JSON.stringify(this.state.items)
)
}
render() {
this.totalTasksElement.textContent = this.state.items.length
this.deleteAllButtonElement.classList.toggle(
this.stateClasses.isVisible,
rhis.state.items.length > 0
)
const items = this.state.filteredItems ?? this.state.items
this.listElement.innerHTML = items.map(({ id, title, isChecked }) => `
<li class="todo_item todo-item" data-js-todo-item>
<input class="todo_item_checkbox" type="checkbox" ${isChecked ? 'checked' : ''} id="${id}" data-js-todo-item-checkbox>
<label class="todo-item_label" for="${id}" data-js-todo-item-label>${title}</label>
<button class="todo_item_delete-button" type="button" aria-label="Delete" title="Delete" data-js-todo-item-delete-button>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 5L5 15M5 5L15 15" stroke="#757575" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
`).join('')
const isEmptyFilteredItems = this.state.filteredItems?.length === 0
const isEmptyItems = this.state.items.lenght === 0
this.emptyMessageElement.textContent =
isEmptyFilteredItems ? 'Tasks not found'
: isEmptyItems ? 'There are no tasks yet'
: ''
}
addItem(title) {
this.state.items.push({
id: crypto?.randomUUID() ?? Date.now().toString(),
title,
isChecked: false,
})
this.saveItemsToLocalStorage()
this.render()
}
deleteItem(id) {
this.state.items = this.state.items.filter((item) => item.id !== id)
this.saveItemsToLocalStorage()
this.render()
}
toggleCheckedState(id) {
this.state.items = this.state.map((item) => {
if (item.id === id) {
return {
...item,
isChecked: !item.isChecked,
}
}
return item
})
this.saveItemsToLocalStorage()
this.render()
}
filter() {
const queryFormatted = this.state.searchQuery.toLowerCase()
this.state.filteredItems = this.state.items.filter(({ title }) => {
const titleFormatted = title.toLowerCase()
return titleFormatted.includes(queryFormatted)
})
this.render()
}
resetFilter() {
this.state.filteredItems = null
this.state.searchQuery = ''
this.render()
}
bindEvents() {
}
}
new Todo()