Как создать приложение To‑Do с CRUD на JavaScript и работе с DOM

Введение
Document Object Model (DOM) — это представление структуры и содержимого веб‑страницы в виде объектов. С помощью JavaScript вы можете получить доступ ко всем элементам DOM и динамически создавать, читать, изменять и удалять их (CRUD).
Эта статья пошагово объясняет, как выполнять CRUD‑операции на примере списка задач. Предполагается, что вы знакомы с основами HTML и JavaScript.
Краткая идея DOM‑манипуляций
Работа с DOM обычно идёт по схеме: найти элемент, подписаться на события, в обработчике менять состояние приложения и отображение. Пример для кнопки:
В этом примере переменная submitButton ссылается на HTML‑элемент кнопки. При клике срабатывает обработчик и появляется всплывающее окно.
Макет страницы: HTML и TailwindCSS
Ниже — минимальная верстка приложения. Элементы имеют id, чтобы к ним можно было обращаться из JavaScript. Для стилизации используется TailwindCSS через CDN; можно заменить на любой CSS‑фреймворк.
To-Do List App
Так выглядит интерфейс после первичной стилизации:
Получение элементов и начальное состояние
Первый шаг в JavaScript — получить ссылки на HTML‑элементы и инициализировать массив задач.
const text = document.getElementById("text");
const addTaskButton = document.getElementById("add-task-btn");
const saveTaskButton = document.getElementById("save-todo-btn");
const listBox = document.getElementById("listBox");
const saveInd = document.getElementById("saveIndex");
let todoArray = [];Массив todoArray хранит задачи в памяти. Для сохранения между загрузками используется localStorage.
Добавление задач
При клике на кнопку «Add» задача добавляется в массив и записывается в localStorage. Затем обновляется отображение.
addTaskButton.addEventListener("click", (e) => {
e.preventDefault();
let todo = localStorage.getItem("todo");
if (todo === null) {
todoArray = [];
} else {
todoArray = JSON.parse(todo);
}
if (text.value.trim() === "") return; // игнорируем пустые строки
todoArray.push(text.value.trim());
text.value = "";
localStorage.setItem("todo", JSON.stringify(todoArray));
displayTodo();
});Важно: записывать todoArray в localStorage нужно при каждой модификации — добавлении, удалении или редактировании.
Отображение списка задач
Функция displayTodo читает массив из localStorage и формирует HTML‑код для listBox, добавляя для каждой задачи кнопки редактирования и удаления.
function displayTodo() {
let todo = localStorage.getItem("todo");
if (todo === null) {
todoArray = [];
} else {
todoArray = JSON.parse(todo);
}
let htmlCode = "";
todoArray.forEach((list, ind) => {
htmlCode += `
${list}
`;
});
listBox.innerHTML = htmlCode;
}
// Вызов при загрузке страницы
window.onload = displayTodo;Удаление задач
Кнопка Delete вызывает функцию deleteTodo с индексом задачи. Внутри используется splice для удаления и синхронизация с localStorage.
function deleteTodo(ind) {
let todo = localStorage.getItem("todo");
if (todo === null) return;
todoArray = JSON.parse(todo);
todoArray.splice(ind, 1);
localStorage.setItem("todo", JSON.stringify(todoArray));
displayTodo();
}Редактирование задач
Кнопка Edit вызывает функцию edit, которая заполняет поле ввода текстом выбранной задачи, прячет кнопку добавления и показывает кнопку сохранения.
function edit(ind) {
saveInd.value = ind;
let todo = localStorage.getItem("todo");
if (todo === null) return;
todoArray = JSON.parse(todo);
text.value = todoArray[ind];
addTaskButton.style.display = "none";
saveTaskButton.style.display = "block";
}
saveTaskButton.addEventListener("click", () => {
let todo = localStorage.getItem("todo");
if (todo === null) return;
todoArray = JSON.parse(todo);
let id = saveInd.value;
if (id === "") return;
todoArray[id] = text.value.trim();
addTaskButton.style.display = "block";
saveTaskButton.style.display = "none";
text.value = "";
localStorage.setItem("todo", JSON.stringify(todoArray));
displayTodo();
});Проверка задачи как выполненной (варианты)
Базовый пример не содержит отметки «выполнено», но её легко добавить: можно менять CSS‑класс элемента при клике, хранить флаг done в объекте задачи или хранить список индексов выполненных задач.
Простейшая модель хранения (модификация структуры):
- Вместо строки в массиве хранить объект: { text: ‘задача’, done: false }
- При клике на текст переключать done и перерисовывать список
Этот вариант расширяем и масштабируется для сортировки и фильтров.
Мини‑методология разработки и тестирования
- Начните с простого: текстовые строки и localStorage.
- Добавьте валидацию ввода (не пустая строка, ограничение длины).
- Покройте сценарии: добавление, удаление, редактирование, перезагрузка страницы.
- Добавьте тесты приёмки (см. раздел Критерии приёмки).
- Переходите к объектной модели задач и синхронизации с сервером по мере необходимости.
Когда такая реализация не подойдёт
- Приложение требует совместной работы нескольких пользователей — localStorage не подходит.
- Большой объём данных и необходимость поиска/фильтрации на сервере — нужна база данных.
- Высокие требования по безопасности и аудиту — нужен серверный слой и авторизация.
Альтернативные подходы
- Хранение на сервере через REST API или GraphQL.
- Использование IndexedDB для оффлайн‑вместимости и больших объёмов данных.
- Использование фреймворков (React/Vue/Svelte) для сложного UI и управляемого состояния.
Практические эвристики и ментальные модели
- Single source of truth: одна структура состояния (например, todoArray) и её последовательная сериализация в localStorage.
- UI как представление состояния: изменяйте только состояние, затем перерисовывайте представление через displayTodo.
- Минимальные атомарные операции: каждая кнопка выполняет одну понятную операцию (add/edit/delete/save).
Контрольный список ролей
- Разработчик: сделать валидацию, обработать ошибки парсинга JSON, учесть пустые значения.
- Тестировщик: проверить сценарии добавления/редактирования/удаления и устойчивость UI при непредвидённых данных.
- Дизайнер: обеспечить доступность кнопок и текстовых полей, добавить фокус и aria‑атрибуты.
Критерии приёмки
- При добавлении новой задачи она отображается в списке и сохраняется после перезагрузки.
- При удалении задача исчезает из списка и больше не появляется после перезагрузки.
- При редактировании изменённый текст сохраняется и отображается корректно.
- Пустые задачи не добавляются.
- Интерфейс не ломается при отсутствии данных в localStorage.
Набор тестов и критерии приёмки (коротко)
- Добавление: ввести “Купить хлеб”, нажать Add → элемент появляется, localStorage содержит “Купить хлеб”.
- Удаление: удалить первый элемент → элемент исчезает, localStorage обновлён.
- Редактирование: отредактировать элемент → новое значение появилось и сохранилось.
- Перезагрузка: после перезагрузки страницы все изменения сохраняются.
Decision flow: что делать при пустом localStorage (Mermaid)
flowchart TD
A[Загрузилась страница] --> B{Есть ключ 'todo' в localStorage?}
B -- Да --> C[Парсить JSON в todoArray]
B -- Нет --> D[Инициализировать todoArray = []]
C --> E[Вызвать displayTodo]
D --> E
E --> F[Пользователь взаимодействует: add/edit/delete]
F --> G[Обновить todoArray и localStorage]
G --> EСоветы по расширению и безопасности
- Для совместной работы переходите на бэкенд и аутентификацию.
- Для больших списков используйте виртуальный рендеринг (windowing) или pagination.
- Всегда проверяйте данные при чтении из localStorage: try/catch при JSON.parse.
Шаблон: расширяемая структура задачи
Рекомендуемая структура для расширения функционала:
// Пример структуры задачи
{
id: 'uuid-or-timestamp',
text: 'Описание задачи',
done: false,
createdAt: 1670000000000,
tags: ['важное','дом']
}При такой структуре проще добавлять метаданные, фильтры и синхронизацию.
Рекомендации по доступности и локализации
- Добавьте aria‑labels к полям ввода и кнопкам.
- Используйте семантические теги (ul/li для списка задач) вместо div, если это уместно.
- Для интернационализации выносите строки в отдельный словарь.
Резюме
Важно помнить: простая реализация на localStorage отлично подходит для личных и учебных проектов. По мере роста требований стоит перенести логику хранения на сервер, улучшить структуру данных и добавить тесты. Начните с базовой модели, затем итеративно улучшайте: валидация, обработка ошибок, доступность и синхронизация.
Важно: регулярное сохранение состояния и единый источник истины (todoArray) упрощают масштабирование приложения.