Домашнє завдання #14: Мій каталог
Фінальний проєкт блоку JavaScript & DOM — і найважливіше ДЗ курсу дотепер. Ти створиш живий каталог своєї колекції: додавання, позначки, редагування, фільтри та збереження між візитами. Це той самий застосунок, який ти в ДЗ #20 перепишеш на TypeScript, а згодом — на React. Усе, що ти вивчив у блоці, зійдеться тут в одну річ.
Тема — твоя, з ДЗ #5. У прикладах нижче — дефолтна фільмотека FilmShelf: список фільмів, які хочеш подивитись, з позначкою «переглянуто». У колекції книг це буде «прочитано», у рецептах — «спробувано», у подорожах — «відвідано». Механіка однакова для всіх — саме її перевіряє чекер.
Що ти вже маєш
- Тему і контент свого продукту з ДЗ #5
- Стилі та кольори з ДЗ #6
- Досвід роботи з формами та localStorage з ДЗ #13
Файлова структура
hw-14/
├── index.html
├── style.css
├── script.js
└── screenshots/
├── desktop.png
└── mobile.png
Загальний вигляд
┌──────────── FilmShelf ────────────────┐
│ │
│ ┌──────────────────────┐ [Додати] │
│ │ Який фільм додати? │ │
│ └──────────────────────┘ │
│ │
│ [Всі] [Ще не переглянуті] [Переглянуті]
│ │
│ ☐ Дюна: Частина друга [✕] │
│ ☑ Опенгеймер ← перекреслений [✕] │
│ ☐ Все всюди і водночас [✕] │
│ ☐ Памфір [✕] │
│ │
│ Залишилось: 3 фільми │
│ [Очистити переглянуті] │
└───────────────────────────────────────┘
Назви фільтрів, лічильника та кнопок адаптуй під свою тему — головне, щоб поведінка відповідала сценаріям нижче.
Специфікація
1. Додавання елемента
- Поле введення + кнопка «Додати». Спрацьовує і клік на кнопку, і
Enterу полі - Порожній ввід (або самі пробіли) — ігнорується, елемент не створюється
- Після додавання поле очищується і знову отримує фокус — можна одразу вводити наступний
- Новий елемент з'являється у списку без перезавантаження сторінки
Кожен елемент колекції — об'єкт з унікальним id, текстом і статусом (переглянуто чи ні). Усі елементи живуть в одному масиві — він і є «джерелом правди», а список на екрані щоразу малюється з нього заново.
2. Позначка «переглянуто»
- Клік на чекбокс або на назву елемента перемикає статус
- Переглянуті елементи виглядають інакше: перекреслений текст і приглушений колір
- Повторний клік повертає елемент у «ще не переглянуті»
3. Видалення
- Кнопка
✕біля кожного елемента видаляє його зі списку (і з масиву даних, а не тільки з екрана)
4. Редагування
- Подвійний клік на назві вмикає режим редагування: текст перетворюється на поле введення з поточним значенням, фокус у полі, текст виділений
Enterабо клік деінде (втрата фокусу) — зберегти нову назвуEscape— скасувати редагування, повернути стару назву- Якщо нову назву стерли до порожньої — елемент видаляється
5. Фільтри
Три кнопки: Всі / Активні / Завершені (у FilmShelf — «Всі / Ще не переглянуті / Переглянуті»).
- Активний фільтр візуально виділений
- Список показує лише елементи, що відповідають фільтру
- Фільтр не видаляє дані: перемкнувся назад на «Всі» — все на місці
- Дії (додавання, позначки, видалення) працюють коректно при будь-якому активному фільтрі
6. Лічильник
- Під списком — кількість активних (незавершених) елементів
- Слово відмінюється правильно: 1 фільм, 2 фільми, 5 фільмів (не «5 фільми»!). Для своєї теми підбери власні форми: 1 книга / 2 книги / 5 книг
- Оновлюється при кожній зміні списку
7. Очищення завершених
- Кнопка «Очистити переглянуті» видаляє всі завершені елементи одним кліком
- Кнопка видима лише тоді, коли завершені елементи взагалі є
8. localStorage
- Колекція зберігається в localStorage після кожної зміни (додавання, позначка, редагування, видалення)
- Після перезавантаження сторінки (
F5) список повністю відновлюється — разом зі статусами
Підказки
Рівень 1: напрямок думки
- Не редагуй DOM «точково» після кожної дії. Простіша архітектура: будь-яка дія змінює масив даних, а потім одна функція перемальовує весь список з масиву. Змінив дані → перемалював → зберіг
- Не вішай окремий слухач на кожну кнопку
✕— елементи ж з'являються і зникають. Один слухач на весь список вирішує це елегантніше - Фільтр — це не «сховати елементи», а «намалювати лише потрібні»: тримай поточний фільтр у змінній і застосовуй його під час перемальовування
- localStorage зберігає лише рядки — масив об'єктів доведеться перетворювати туди й назад
Рівень 2: які інструменти використати
- Делегування подій: один
addEventListenerнаul, всередині —e.target.closest()щоб знайти елемент списку, і перевірка класів цілі, щоб зрозуміти, куди саме клікнули (чекбокс, текст чи кнопка видалення) data-атрибути +dataset: зберігайidелемента колекції прямо наli— так обробник знає, з яким об'єктом масиву працюватиDate.now()— простий спосіб отримати унікальнийid- Для редагування:
replaceWith()міняє span на input,focus()+select()готують поле до вводу; подіїkeydownтаblurзавершують редагування - Відмінювання: подивись на останню цифру числа (і виняток 11–14)
JSON.stringify/JSON.parse— серіалізація для localStorage- Методи масивів з ДЗ #11 —
filter,find,map— тут запрацюють на повну
Рівень 3: псевдокод скелета
масив items = завантажити з localStorage або []
змінна currentFilter = "all"
функція render:
відфільтрувати items за currentFilter
очистити ul і створити li для кожного елемента
(з data-id, чекбоксом, текстом, кнопкою ✕)
оновити лічильник
показати/сховати кнопку очищення
зберегти items у localStorage
додавання:
текст = значення поля без зайвих пробілів
якщо порожній → нічого не робити
додати новий обʼєкт в items → очистити поле → фокус → render
клік по списку (делегування):
знайти li через closest, взяти id з dataset
якщо ціль — чекбокс/текст → перемкнути статус обʼєкта → render
якщо ціль — кнопка ✕ → прибрати обʼєкт з items → render
подвійний клік по тексту:
замінити текст на input зі старим значенням
Enter/blur → зберегти (порожнє → видалити) → render
Escape → просто render (старе значення повернеться з даних)
Бонус (+15)
Реалізуй одну або кілька з цих можливостей:
- Drag & Drop: зміна порядку елементів перетягуванням (HTML5 Drag and Drop API:
draggable,dragstart,dragover,drop; уdragoverобов'язковийpreventDefault). Новий порядок теж зберігається в localStorage - Теги/категорії: до елемента можна додати тег («бойовик», «комедія» — під свою тему), теги показуються кольоровими мітками, є фільтрація за тегом
- Дати: поле дати при додаванні (дата виходу, дедлайн перегляду), прострочені підсвічуються, є сортування за датою
Як здати
- Робота — у твоєму репозиторії курсу
my-web-course, у папціhw-14/ - Мінімум 3 коміти з осмисленими повідомленнями (не "fix" і не "update") — чекер бачить git-історію
- У папці —
README.md: 2-3 речення, що зроблено і як запустити - Скріншоти в
hw-14/screenshots/:desktop.png— каталог на ширині ~1440px, у списку є і активні, і завершені елементиmobile.png— на ширині ~390px
- Надішли посилання на репозиторій (можна одразу на папку
hw-14)
Критерії оцінки
| Критерій | Тип | Бали |
|---|---|---|
| Додавання: кнопка + Enter, порожній ввід ігнорується, поле очищується і отримує фокус | [поведінка] | 15 |
| Позначка «переглянуто»: toggle по кліку, перекреслення + приглушення завершених | [поведінка] | 10 |
| Видалення елемента кнопкою ✕ (з даних, не лише з екрана) | [поведінка] | 10 |
| Редагування: dblclick → input, Enter/blur зберігає, Escape скасовує, порожнє → видалення | [поведінка] | 15 |
| Фільтри Всі/Активні/Завершені, активний фільтр виділений, дані не втрачаються | [поведінка] | 10 |
| Лічильник активних з правильним відмінюванням (1 фільм / 2 фільми / 5 фільмів) | [код] | 10 |
| «Очистити завершені» працює і показується лише коли є завершені | [поведінка] | 5 |
| localStorage: збереження після кожної зміни, повне відновлення після F5 | [код] | 15 |
| Каталог у стилі твого продукту; на desktop.png видно перекреслені завершені елементи; mobile.png без поламаної верстки | [скрін] | 10 |
| Бонус: Drag & Drop / теги / дати | [поведінка] | +15 |
Що далі
Це ДЗ — фундамент половини курсу, що залишилась. У ДЗ #20 ти перепишеш цей каталог на TypeScript — бережи код і не видаляй репозиторій. А в React-блоці він переродиться втретє — вже з реальним API твоєї теми.