Форма на странице контактов — та самая, о которой идёт речь. Дальше разберём её по частям: из чего собрано поле, кто проверяет введённое и что нужно дописать, чтобы письмо действительно уходило.
Зачем форма, если есть почта
Адрес, написанный на странице, за неделю собирают роботы, и дальше на него идёт спам. Форма адрес не показывает: он лежит в настройках сайта, а посетитель просто пишет сообщение.
Второе: форма задаёт вопросы. Свободное письмо часто приходит без имени и без обратного адреса, а поля заставляют указать и то, и другое.
Поле — это не только input
Минимальный набор для одного поля — четыре части: подпись,
само поле, при необходимости подсказка и место под сообщение
об ошибке. Подпись обязана быть отдельным тегом
<label>, связанным с полем: тогда щелчок
по подписи ставит курсор в поле, а программа чтения экрана
называет поле по имени.
<div class="field">
<label class="field__label" for="email">Почта</label>
<input class="field__input" id="email" name="email" type="email"
required autocomplete="email"
aria-describedby="email-hint"
data-msg-required="Без адреса я не смогу ответить"
data-msg-type="Похоже, в адресе опечатка: нужен знак @">
<p class="field__hint" id="email-hint">На него придёт ответ</p>
<p class="field__error" hidden></p>
</div>
Тексты ошибок лежат прямо в атрибутах поля —
data-msg-required и остальные. Так они правятся
вместе с полем, а не в скрипте: добавили поле — написали
рядом, что сказать, если его не заполнят.
Атрибут autocomplete — не мелочь
С ним браузер подставляет имя и почту одним касанием. На телефоне это разница между «отправил за пять секунд» и «набирал адрес пальцем и бросил».
Кто проверяет: браузер, скрипт и сервер
Проверок всегда три, и заменять одну другой нельзя.
| Кто | Что делает | Зачем нужен |
|---|---|---|
| Браузер | Смотрит на required, type, minlength |
Работает всегда, даже если скрипт не загрузился |
| Скрипт | Показывает понятные подсказки рядом с полем | Свои тексты вместо казённых фраз браузера |
| Сервер | Проверяет всё заново перед отправкой письма | Первые две проверки обходятся в две минуты |
Отсюда важная мелочь: атрибут novalidate, который
отключает проверку браузера, ставит скрипт, а не разметка. Пока
скрипт не отработал, форму проверяет браузер — и без JavaScript
она остаётся рабочей.
Когда показывать ошибку
Не раньше первой попытки отправить. Красная рамка на поле, в котором человек ещё набирает адрес, — это придирка к незаконченному действию. После неудачной отправки правила меняются: теперь поле перепроверяется на каждое изменение, чтобы подсказка исчезла сразу, как только ошибка исправлена.
Ещё две вещи делает скрипт при неудачной отправке: связывает
сообщение с полем через aria-describedby, ставит
aria-invalid и переводит фокус на первое неверное
поле. Без последнего человек с программой чтения экрана
останется у кнопки и просто не узнает, что где-то выше есть
ошибка.
Что попросить у нейросети
Напиши скрипт проверки формы обратной связи на чистом
JavaScript. Поля: имя, почта, сообщение, чекбокс согласия.
В разметке уже стоят настоящие условия —
required, type="email",
minlength, а тексты ошибок лежат в атрибутах
поля вида data-msg-required.
Правила: атрибут novalidate форме ставит сам
скрипт, чтобы без JavaScript работала проверка браузера.
До первой попытки отправить подсказки не показывать, после
неё перепроверять поле при вводе и потере фокуса. Ошибку
связать с полем через aria-describedby,
не затирая уже указанные там подсказки, и поставить
aria-invalid.
После неудачной отправки перевести фокус на первое неверное
поле. Блок с результатом сделать role="status",
причём текст в него писать следующим кадром, уже после
снятия hidden. Комментарии по-русски.
Про role="status". Ваше
требование писать текст следующим кадром верное, и причина
такая: изменение внутри скрытого блока программы чтения
экрана не объявляют. Снимаете hidden, затем
в requestAnimationFrame кладёте текст — тогда
сообщение прозвучит.
Что взять у браузера даром. У каждого поля
есть объект validity: он сам говорит, пусто ли
поле (valueMissing), не подходит ли формат
(typeMismatch), коротко ли значение
(tooShort). Свои правила писать не нужно —
достаточно сопоставить эти признаки вашим атрибутам
с текстами.
Про чекбокс согласия. Его сообщение ставьте под всей группой, а не сбоку от квадратика: иначе на телефоне строка ошибки выдавливает подпись чекбокса за край экрана.
Отправка письма в Evolution CMS
Вся разметка выше писем не отправляет: HTML умеет только собрать данные и передать их туда, где работает программа на сервере. В Evolution такая программа — сниппет. В поставке 3.5.8 его нет, и отсюда развилка: поставить готовый или написать свой.
Готовый: поставить из каталога
В админке есть модуль Extras — каталог
расширений на 171 пакет: нашли, нажали «Установить», настроили
параметрами и чанками. С формами там вот что: eForm
(22 тысячи установок), FormLister и
easyForm помечены «устаревшие» —
их писали под прошлые ветки движка. Из свежего есть
sMailer, но это не форма, а служба отправки
писем: принять поля и проверить их всё равно придётся самому.
Так что выбор не «готовое против своего», а «устаревшее
готовое против ста своих строк».
Свой: сто строк и метод ядра
Этот сайт пошёл вторым путём: всё сложное уже есть в ядре.
Метод getMail() отдаёт готовый почтовый объект,
настроенный по разделу «Почта» в конфигурации сайта, — сам
соединяется с сервером и кодирует русские буквы в теме.
Остаётся проверить поля и попросить его отправить.
Чанк chunk-contact-form — та же вёрстка формы,
что в прототипе, плюс места для введённых значений, сообщений
у полей и строки результата. Адрес получателя берётся из
настройки сайта emailsender: в разметку страницы
он не попадает, и роботы его не увидят.
Скобки тут другие
Форма обязана считаться заново на каждый заход, поэтому
вызов некэшируемый — . Запись
[[!Сниппет]], привычная по другим движкам,
в Evolution означает сниппет с именем «!Сниппет»:
он не находится, и на месте формы выходит пустое место
без единого сообщения об ошибке.
Что обязан делать обработчик
Код сниппета открыт, и это не делает сайт уязвимым: защита обязана держаться на самом коде, а не на том, что его не видно. Прятать нужно только данные — пароль от почты и адрес получателя. Вот что в нём есть и зачем.
| Что сделано | Что случится, если не сделать |
|---|---|
| Поля проверяются заново | Проверка в браузере обходится за две минуты: JavaScript отключается, письмо приходит пустым |
| Получатель — только из параметра вызова | Если брать адрес из скрытого поля формы, любой подставит свой и разошлёт спам с вашего сервера |
| Из темы и имени вырезаны переносы строк | Через перенос в письмо дописывают свои заголовки и добавляют получателей |
| Письмо уходит от адреса сайта, посетитель — в «ответить» | Письмо от чужого адреса почтовый сервер сочтёт подделкой и не пропустит |
| Значения возвращаются в поля экранированными | Иначе отправитель впишет в поле тег и выполнит свой скрипт на вашей странице |
| Поле-ловушка и пауза между письмами | Робот заполняет всё подряд и шлёт без остановки; ловушка отсекает первое, пауза — второе |
Частые ошибки
| Ошибка | Чем плохо | Как правильно |
|---|---|---|
| Подпись — плейсхолдер внутри поля | Пропадает при вводе, и человек забывает, что тут было | Отдельный label, связанный с полем |
novalidate стоит в разметке |
Без JavaScript форма не проверяется вообще | Атрибут ставит скрипт при запуске |
| Ошибка показана только цветом рамки | Человек не видит цвета или не понимает, что не так | Текст рядом с полем плюс aria-invalid |
| Проверка только на странице | Обходится отключением JavaScript за две минуты | Обязательная проверка на сервере |
| «Письмо отправлено», когда его нет | Человек ждёт ответа, которого никто не получал | Сообщать правду о том, что произошло |
Что должно получиться
- Щелчок по подписи ставит курсор в поле
- До первой отправки форма не ругается, после — подсказки исчезают по мере исправления
- Фокус после неудачной отправки стоит на первом неверном поле
- С выключенным JavaScript форму по-прежнему проверяет браузер
- Почтовый адрес получателя нигде не выведен на страницу