Вивчай
Домашнє завдання #14 · Фінальний проєкт блоку: DOM, події, форми, localStorage
100 балів+15 бонусintermediate

Домашнє завдання #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
  • Теги/категорії: до елемента можна додати тег («бойовик», «комедія» — під свою тему), теги показуються кольоровими мітками, є фільтрація за тегом
  • Дати: поле дати при додаванні (дата виходу, дедлайн перегляду), прострочені підсвічуються, є сортування за датою

Як здати

  1. Робота — у твоєму репозиторії курсу my-web-course, у папці hw-14/
  2. Мінімум 3 коміти з осмисленими повідомленнями (не "fix" і не "update") — чекер бачить git-історію
  3. У папці — README.md: 2-3 речення, що зроблено і як запустити
  4. Скріншоти в hw-14/screenshots/:
    • desktop.png — каталог на ширині ~1440px, у списку є і активні, і завершені елементи
    • mobile.png — на ширині ~390px
  5. Надішли посилання на репозиторій (можна одразу на папку 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 твоєї теми.