Перейти к содержимому

Форма загрузки файлов: 5 починок без редизайна

Форма загрузки файлов: 5 починок без редизайна

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

Код: Oat-Upload, лицензия MIT.

Oat Upload это небольшое расширение библиотеки Oat UI. Оно оборачивает нативный <input type="file"> и добавляет зону перетаскивания, превью, удаление, проверку и прогресс. Зависимостей в рантайме нет, сборки на сервере нет, форма отправляется, даже если скрипт не загрузился.

Где форма загрузки файлов теряет человека

Пять провалов повторяются из проекта в проект, и сама загрузка среди них не виновата.

Человек не видит, что прикрепил. Камера телефона называет файлы IMG_4821. Без превью не тот документ уезжает в поддержку, и через день его правят руками.

Про лимит сообщают после попытки. «До 5 МБ» мелким серым шрифтом под полем это подсказка, а подсказка не проверка. Компонент сравнивает размер до того, как запрос ушёл из браузера, поэтому отказ стоит секунды, а не круга до сервера и обратно.

Ошибка стирает выбор. Отклонённый файл с очисткой поля заставляет выбирать всё заново. Здесь отклонённый файл выпадает из списка, остальные остаются.

На медленной связи не происходит ничего. Без индикатора сорок секунд загрузки выглядят зависшей страницей, и человек перезагружает вкладку. Перезагрузка отменяет загрузку.

Удалить файл нельзя. Прикрепили не тот договор, и единственный выход это перезагрузка страницы, которая уносит остальную форму.

Поле остаётся источником истины

<ot-upload> не заменяет поле, а растёт вокруг него:

<ot-upload max-size="5242880" max-files="5">
  <input type="file" name="attachments" multiple accept="image/*,.pdf">
</ot-upload>

Это вся интеграция. Форма отправляет attachments так же, как раньше, сервер держит свою проверку, а разработчик в шаблоне видит поле файла, а не фреймворк.

Список файлов на странице и список в браузере синхронизируются через DataTransfer там, где браузер это умеет. Превью и отправка не расходятся.

Проверка, которая говорит до запроса

Проверка читает сначала нативный input, потом атрибуты <ot-upload>, поэтому accept, multiple и disabled ведут себя так, как определил браузер. max-size и max-files закрывают два лимита, которые браузер сам не держит.

Отклонённый файл вызывает ot-upload-error с причиной type, size, count или disabled и сообщением для человека. Из этой причины форма собирает «PDF весит 12 МБ, лимит 5 МБ» вместо «недопустимый файл».

Прогресс и удаление без спиннера

Прогресс это нативный <progress>, который обновляет setProgress(). Код загрузки передаёт число от 0 до 100, и полоса показывает реальное положение дел, а не анимацию. Вызов с null или resetProgress() прячет её.

Удаление это кнопка в строке, а remove() принимает индекс, файл или идентификатор элемента. Поле обновляется вместе со списком, поэтому на сервер уходит то, что человек видит.

Для команд со своей разметкой части названы явно: data-upload-dropzone, data-upload-list, data-upload-error, data-upload-progress, data-upload-status. Компонент заполняет то, что дали, и собирает остальное, если не дали ничего.

Где это не подходит

Загрузка видео на 2 ГБ, докачка после обрыва сети и передача кусками в S3 это другая задача, и компонент вокруг нативного поля её не решает. Десять тысяч файлов в одном запросе тоже другая задача, и ответ там это очередь на сервере и другой интерфейс.

Для формы с пятью вложениями рамка верная, и страница остаётся страницей.

Сколько это стоит

Одна таблица стилей, один скрипт, MIT, без зависимостей. Для проекта по 30–50 $ в час это утро работы, и оно убирает класс обращений в поддержку, которые никто не выставляет в счёт: обращения про файлы.

Рядом: веб-разработка о том, как считается объём формы, и сортировка таблицы HTML о том же доводе в пользу нативной разметки вместо её замены.