Эта страница переведена с английского. Читать страницу на английском
Form::of(value) редактирует модель или entity. Поля берутся из ссылок на поля, подписи из файлов локалей, а проверка из атрибутов #[rule], поэтому браузер и сервер проверяют одни и те же правила.
Форма
Form::of(Contact { quantity: 1, ..Contact::default() })
.row(|r| r.field(Contact::name).field(Contact::email))
.row(|r| r.field(Contact::quantity).field(Contact::size))
.submit(t!("docs.live.send"), send_contact)
.on_success(|_, ui| ui.toast(t!("docs.live.sent")))Оставьте имя пустым или введите неверный адрес электронной почты и нажмите Send: браузер сразу покажет ошибку, а сервер отклонил бы тот же ввод с тем же сообщением.
Раскладка
| Вызов | Результат |
|---|---|
.field(M::f) | Одно поле на отдельной строке |
.row(|r| r.field(a).field(b)) | Поля рядом; на узких экранах они выстраиваются друг под другом |
.section(title) | Группа полей после неё с заголовком |
.step(title) | Шаг мастера; форма показывает по одному шагу |
.range(M::from, M::to) | Две даты как один выбор диапазона |
Form::auto(value) | Все поля в порядке объявления |
Сохранение
.submit(label, action) называет action, который сохраняет данные. Action должен принимать Valid<M> для модели формы; иначе код не компилируется. Runtime отправляет значения, сервер выполняет все правила, затем action.
- Ошибки полей от правил или от
ValidationErrorsв action появляются рядом с полями. Error::Conflict(устаревший#[version]) показывает сообщение о том, что запись изменил кто-то другой..on_success(|result, ui| ...)выполняет эффекты после успешного сохранения (см. Effects и окна).
Элементы управления
У каждого типа поля есть элемент управления по умолчанию: текстовые поля, числовые поля, деньги с валютой, выбор даты и времени, флажки для bool и выпадающие списки для choice. .control(M::f, Control::...) выбирает другой, а .lookup(M::f, action) ищет записи на сервере. См. Controls and pickers.
Поля только для чтения
Поля #[readonly] показываются в формах, но не редактируются, например остаток на складе, который меняют только движения.
Загрузка файлов
Поля File, Option<File> и Vec<File> хранят файлы, которыми управляет FileStore. Объявите правила один раз как const, зарегистрируйте хранилище в Feature с разрешением и используйте те же правила в форме:
pub const ATTACHMENTS: FileRules = FileRules::new("attachments")
.max_bytes(5 * 1024 * 1024)
.max_files(5)
.accept(&[FileType::Pdf, FileType::Png, FileType::Jpeg]);
Feature::new("support")
.files(ATTACHMENTS, "support.ticket.files", DiskStore::new("data/attachments"))
.scan_files(MyVirusScanner)
Form::of(ticket).field(Ticket::title).file(Ticket::attachments, ATTACHMENTS)Элемент управления это встроенное поле выбора файла внутри зоны перетаскивания. Каждый файл загружается отдельно с индикатором хода, его можно отменить во время загрузки, повторить после сбоя и удалить после завершения. Для изображений показывается превью, созданное из object URL выбранного файла. Ход загрузки и результаты объявляются в вежливой live-области (polite live region). В showcase есть рабочий пример по адресу /uploads; на этом сайте документации нет вошедшего пользователя, поэтому живой загрузки здесь нет.
| Клавиша | Что делает |
|---|---|
| Tab | Переходит к полю выбора файла, затем к ссылке скачивания каждого файла и к его кнопке Cancel, Retry или Remove |
| Enter или Space на поле выбора файла | Открывает системное окно выбора файла (путь с клавиатуры для перетаскивания) |
| Enter или Space на Cancel, Retry, Remove | Выполняет действие; после Cancel фокус переходит на Retry, после Remove на следующий файл или обратно на поле |
Что сервер делает для POST /_zeyn/files/{store}:
- проверяет заголовок CSRF, разрешение хранилища и guard Feature и запускает перехватчики (
CallKind::Upload, аудит с идентификатором нового файла); загрузки учитываются в лимите записи; - отклоняет объявленный
Content-Lengthсверх лимита (413), затем читает первые 4 КиБ и определяет тип по содержимому; расширение должно принадлежать тому же типу, а тип должен входить вaccept, иначе415. HTML, SVG, XML и скрипты не принимаются никогда. Тип содержимого, заявленный браузером, игнорируется; - передаёт остальное в хранилище потоком порциями не более 64 КиБ через канал на четыре элемента, подсчитывая байты; превышение лимита, обрыв соединения или 30 секунд без данных останавливают загрузку, и хранилище удаляет то, что записало;
- запускает
FileScannerFeature; отклонённый файл или сканер, который дал сбой, приводят к удалению файла и ответу422; - отвечает
201с{id, name, size, type}. Идентификатор это 128 случайных бит, созданных сервером; из имён удаляются пути и управляющие символы.
Скачивания (GET /_zeyn/files/{store}/{id}) проверяют форму идентификатора, разрешение, guard и арендатора (tenant) и проходят через выдачу медиа (см. docs/media.md): диапазоны, entity tag и nosniff. Встроенно (inline) отдаются только растровые изображения, аудио и видео; всё остальное отправляется как application/octet-stream с Content-Disposition: attachment, с именем в кавычках в ASCII плюс именем в UTF-8, под Content-Security-Policy: sandbox.
DiskStore хранит {id}.bin и {id}.json в одной папке и сначала записывает загрузки в {id}.part. MemoryStore держит файлы в памяти с ограничением по байтам, для тестов и демонстраций. Значение формы лишь называет файлы: action сохранения, которому нужно быть уверенным, что файл существует, вызывает store.meta(id), как это делает showcase.
Бюджет: загрузка 256 МиБ увеличивает кучу сервера примерно на 0,4 МиБ (tests/files_memory.rs); элемент управления отрисовывает 10 узлов DOM плюс 5 на каждый файл; files.js добавляет 12 КБ в исходном виде и 4 КБ в gzip, files.css 3 КБ в исходном виде и 1 КБ в gzip.