Форма, которая не теряет написанное
Черновик в браузере помогает сохранить работу при сбое, но не подтверждает, что сайт получил сообщение. По надписи «Сохранено» эту разницу не понять. Форме нужно сообщать, где остался текст и что с ним можно сделать дальше: восстановить, отправить или вернуться к уже принятому сообщению.
Для локальной копии в статье о формах я предлагал использовать localStorage. Такая копия помогает пережить перезагрузку и вернуться к работе в том же браузере. Она полезна и до первой отправки, и после неудачной попытки.
Я предпочитаю назвать место сохранения прямо: «Черновик сохранён в этом браузере». После отправки нужна другая формулировка, например «Сообщение принято», и она появляется только после подтверждения сайта. Если отправленная запись доступна по ссылке, её можно показать здесь же. По ней можно вернуться к сообщению и проверить, что именно отправлено.
Даже локальная запись может не получиться. Браузер ограничивает объём хранилища, пользователь может запретить его использование, а сохранённые данные позже удалить. Ошибка возможна уже при обращении к localStorage, поэтому обрабатывать нужно и отказ доступа, и отказ записи. Если сохранить черновик не удалось, форма всё ещё должна позволять написать и отправить сообщение. Только обещание автоматического восстановления последних изменений теперь придётся убрать и объяснить, что они пока остаются на открытой странице. В хранилище при этом может лежать более старая копия.
Сохранять подряд все поля формы я бы не стал. Для длинного несекретного текста черновик полезен. Паролям, платёжным данным и личной переписке нужно другое решение. К localStorage имеют доступ скрипты того же источника, а на общем компьютере к браузеру может вернуться другой человек. Важно заранее решить, какие данные допустимо оставлять после закрытия страницы.
Когда вернуться к черновику
Допустим, посетитель вернулся к форме, и мы нашли его черновик. Автоматически вставить его в поле соблазнительно, но там уже может быть содержимое: его вернул сервер после ошибки проверки или сам браузер. Затирать эту версию локальной копией опасно. Лучше предложить восстановление и показать, от какого времени черновик. Если в поле уже начато новое сообщение, его тоже нужно сохранить до любой замены.
Сам черновик полезно связать с конкретной формой и записью, которую редактируют. Ключ с одним лишь названием поля вроде text смешает разные задачи на одном сайте. Если на сайте есть учётные записи, нужно учитывать и смену пользователя: чужой черновик не должен появляться после выхода и нового входа. Это всё ещё тот же вопрос о том, кому принадлежит сохранённый текст.
Даже правильный ключ не решает конфликт двух вкладок с одной формой: localStorage у них общий. Чтобы одна вкладка не затёрла работу в другой, версии можно хранить отдельно и предлагать выбрать нужную.
В форме нужна возможность удалить черновик. Иначе очищаешь поле, возвращаешься позже и снова видишь текст, от которого уже отказался. Пустое поле может быть осознанным изменением, поэтому восстанавливать старое содержимое при каждом его появлении неправильно.
Самая неудобная ошибка
Потеря связи во время отправки оставляет нас с неопределённостью. Запрос мог не дойти. Мог дойти и завершиться, а ответ потеряться по дороге обратно. По одному сообщению браузера мы не узнаем, какой случай произошёл.
Здесь не стоит писать «Сообщение не отправлено», если мы этого не знаем. Если локальная запись успешна, точнее сказать: «Не удалось получить подтверждение. Ваш текст сохранён в этом браузере».
Возможность повторить отправку остаётся, но если сервер создаёт новую запись при каждом запросе, повтор после обрыва может привести к дубликату. Для формы, где это существенно, клиент и сервер могут договориться об идентификаторе одной отправки. Повтор с тем же идентификатором и содержимым должен возвращать результат уже принятого действия. Если первый запрос ещё выполняется, второй не должен создавать ещё одну запись. Чтобы повторить запрос после перезагрузки, до отправки нужно сохранить и идентификатор, и отправляемую версию текста отдельно от дальнейших правок. Повторяем именно эту версию; изменённый текст под прежним идентификатором сервер должен отклонить. Новому сообщению нужен новый идентификатор.
Срок хранения идентификаторов задаётся на сервере. После его истечения прежний ключ уже не гарантирует защиту от дубликата, поэтому обещать безопасный повтор сколько угодно времени нельзя. Блокировка кнопки поможет от случайного двойного нажатия, но не от повтора после перезагрузки страницы.
После успешной отправки есть ещё одна ловушка. Пока запрос выполнялся, автор сообщения мог продолжить редактирование. Удалив черновик и очистив поле после получения ответа, мы потеряем изменения, которых в отправленном тексте не было. Нужно помнить, какую версию отправляли, и очищать только её. Если версия уже другая, подтверждение относится к прежнему сообщению, а новый текст остаётся у пользователя.
Начать проверку можно с небольшой формы: отключить сеть во время отправки, запретить локальное хранение, перезагрузить страницу или дописать фразу до прихода ответа. Отдельно нужен случай, когда сообщение уже принято, но подтверждение не дошло: произвольное отключение сети не гарантирует, что мы попали именно в этот момент. В каждом случае важно, какой текст остался в форме или черновике и что форма сообщила об отправке. Если форма сохранила новую фразу, но подписала её «Отправлено», данные мы уберегли, а отправителя всё равно запутали. По надписи рядом с полем должно быть понятно, что произошло именно с этим текстом.