Проект: список задач — план и разметка

проект≈ 6 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Проект начинается не с кода, а с трёх решений: что приложение умеет, из каких файлов состоит и как выглядит одна задача — например, { id, title, done }. Разберёмся с ними сейчас, чтобы дальше оставалось только печатать.

Разделы темы:·

Задание звучит скучно: «сделать список дел». Пока оно сформулировано так, делать можно что угодно — и через час выяснится, что кнопка получилась красивая, а задачи всё ещё не добавляются. Поэтому первый шаг — превратить идею в список требований, где каждое можно проверить руками.

Что умеетЧто это значит на практике
Добавить задачутекст из поля попадает в список, поле очищается
Отметить выполненнойклик по чекбоксу меняет флаг и вид строки
Удалить задачукнопка рядом со строкой убирает задачу насовсем
Пережить перезагрузкупри старте список читается из хранилища браузера
Показать только нужныепереключатель «все / активные / сделанные»

Первые три строки — это MVP, минимум, после которого приложением уже можно пользоваться. Две последние дописываются поверх работающего кода, а не вместо него. Отметим это прямо в коде — так виднее, где сегодняшняя граница.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Требования отвечают на вопрос «что», шаги — на вопрос «в каком порядке». Хороший шаг заканчивается чем-то проверяемым: открыл страницу — увидел результат. Если после шага смотреть не на что, шаг выбран неудачно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Порядок не случаен: каждый следующий шаг опирается на предыдущий. Нельзя нарисовать список, пока не решено, из чего он состоит, и нечего добавлять, пока нечем показать добавленное. Шаги «хранилище» и «фильтры» встанут в конец очереди — они меняют только источник данных и то, какую часть массива мы рисуем.

Не садитесь писать код, пока не можете назвать шаг, который делаете прямо сейчас. Если название расплывается — шаг слишком большой, поделите его надвое.

HTML для проекта нужен скромный: форма с полем ввода и кнопкой, а под ней пустой <ul>. Ни одной <li> руками не пишем — все строки создаёт JavaScript. Иначе тестовая задача «ааа проверка» однажды доедет до живого сайта.

Каждой строке нужен опознавательный знак — атрибут data-id. По нему обработчик поймёт, какую именно задачу отметили или удалили: не по тексту (тексты повторяются) и не по позиции (позиции сдвигаются).

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

<form> бесплатно даёт две вещи: Enter в поле отправляет её, а атрибут required не пропустит пустую задачу. Взамен в обработчике придётся вызвать event.preventDefault() — без него браузер по привычке перезагрузит страницу, и список исчезнет на глазах.

Кнопке «Добавить» ничего дописывать не нужно: <button> без атрибута type внутри формы по умолчанию её отправляет, а кликабельный <div> на том же месте форму не отправил бы. Чем настоящая кнопка отличается от самодельной, разобрано в прологе, в статье про кнопки, ссылки и подписи.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Файлов будет три: index.html, style.css, app.js. Сборщики, модули и папка src появятся в других проектах — здесь они добавили бы шагов, но не понимания.

ФайлЧто внутри
index.htmlкаркас: форма, пустой список, подключение стилей и скрипта
style.cssвнешний вид: отступы, зачёркнутый текст у выполненных задач
app.jsданные и логика: массив задач, отрисовка, обработчики событий

Начинается index.html со скелета, общего для любой страницы: doctype, служебный <head> с кодировкой и названием вкладки и видимый <body>. Зачем нужна каждая его строка, разобрано в прологе, в статье про скелет страницы.

Скрипт подключаем с атрибутом defer: браузер скачает файл параллельно с разметкой, а выполнит, когда страница уже собрана. Так код не будет искать список, которого ещё нет. Внутри app.js — одно состояние и одна функция запуска.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Самое важное решение проекта — как выглядит одна задача. Договоримся: это объект с тремя полями. id — число, уникальное и неизменное; title — строка; done — true или false, и никаких «да», «нет» и пустых строк.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Весь список — массив таких объектов, и он единственный источник правды. Экран лишь отражает массив: поменяли данные — перерисовали. Обратное направление, когда состояние вычитывают из текста строк на странице, работает ровно до первой задачи с названием «Удалить».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Индекс — это адрес в очереди, а не имя. Стоит удалить одну задачу, и все следующие переезжают: обработчик, запомнивший «вторая строка», начинает попадать не туда.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

  • Сначала требования и порядок шагов, потом код: план ловит ошибки дешевле, чем отладка.
  • MVP проекта — добавить, отметить, удалить; хранение и фильтры дописываются поверх работающего.
  • Разметка минимальная: форма с полем и кнопкой плюс пустой список; строки создаёт JavaScript, у каждой есть data-id.
  • Три файла — index.html, style.css, app.js; скрипт подключается с defer, чтобы код работал с готовой страницей.
  • Задача — объект { id, title, done }, а массив таких объектов и есть единственный источник правды.

1/3Почему у задачи заводят отдельное поле id, а не пользуются её местом в массиве?

2/3Что выведет console.log(JSON.stringify({ id: 1, done: false }));?

3/3Где в нашем проекте хранится состояние — сам список задач?

Опишите одну задачу

Напишите функцию makeTask(title): она возвращает объект задачи с полями id, title и done. Идентификаторы выдаются по порядку начиная с единицы, лишние пробелы вокруг названия обрезаются, новая задача всегда невыполненная.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Счётчик lastId живёт снаружи функции: объяви его внутри — он обнулялся бы при каждом вызове, и все задачи получили бы id, равный единице.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Обрезка через trim() делается один раз, при создании: дальше по проекту можно считать, что в title лежит уже чистая строка.

Строка статуса

По массиву задач соберите строку вида «Осталось: 1 из 2» — сколько задач ещё не выполнено и сколько их всего. Функция должна работать с любым массивом такого формата.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Невыполненные отбираем фильтром, общее число берём из длины массива.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

На пустом списке получится «Осталось: 0 из 0» — на этом месте позже появится подсказка «Пока пусто», и функция статуса просто не будет вызываться.