Эта страница переведена с английского. Читать страницу на английском
Поля формы выбирают элемент управления по своему типу. .control(M::f, Control::...) меняет его, а .lookup(M::f, action) превращает поле в поиск на сервере. Каждый элемент управления внутри это нативный элемент формы, поэтому значения отправляются одинаково с runtime и без него, а каждый элемент следует своему шаблону ARIA для пользователей клавиатуры и программ чтения с экрана.
Form::of(Contact { quantity: 2, size: Size::Medium, ..Contact::default() })
.lookup(Contact::product, find_samples)
.row(|r| r.field(Contact::delivery).field(Contact::size))
.control(Contact::delivery, Control::Radio)
.control(Contact::size, Control::Segmented)
.field(Contact::newsletter)
.control(Contact::newsletter, Control::Switch)
.field(Contact::note)
.submit(t!("docs.live.send"), send_contact)Элементы по умолчанию
| Тип поля | Элемент управления |
|---|---|
String | Текстовое поле; растущая текстовая область со счётчиком символов, когда max_len больше 255 |
i32, i64 | Числовое поле с правилами min и max |
Money | Сумма в числовом формате пользователя, с валютой |
Decimal | Точное число в формате пользователя, с scale поля (см. Ввод Decimal) |
bool | Флажок |
enum с #[choice] | Select: кнопка, открывающая список с поиском |
Vec из enum с #[choice] | Множественный выбор: чипы и список, где выбирают несколько вариантов |
Date, Time, DateTime, LocalDateTime | Выбор даты и времени |
Vec<M> модели | Редактор строк (см. Редактор строк) |
Другие элементы управления
| Элемент управления | Для чего |
|---|---|
Control::Radio | Выбор из нескольких вариантов, все видны |
Control::Segmented | Выбор из двух-пяти коротких вариантов |
Control::Listbox | Длинный список выбора с поиском по вводу |
Control::Switch | bool, который действует как настройка |
Control::Percent | Decimal или целое число со знаком процента |
Control::Password, Control::NewPassword | Текстовое поле типа пароль, с кнопкой показать и скрыть |
Control::PasswordMeter | Новый пароль с индикатором надёжности |
Control::Rating | Целое число от 1 до N, показанное звёздами (см. Rating) |
Rating
Control::Rating показывает целочисленное поле в виде звёзд от 1 до N, где N задаётся правилом max поля (5 без правила, не более 10). Это группа нативных радиокнопок внутри fieldset, чья legend является подписью поля, поэтому без скрипта она следует паттерну W3C APG radio group: Tab попадает в группу один раз, стрелки перемещают и отмечают, а каждая звезда называется «3 из 5» на языке пользователя. При наведении подсвечиваются звёзды до указателя. Сервер проверяет значение правилами поля, как любое число, поэтому задайте полю min и max; при required для Option<i64> пустой выбор отклоняется.
#[model]
pub struct Feedback {
#[rule(required, min = 1, max = 5)]
pub stars: Option<i64>,
}
Form::of(Feedback::default())
.field(Feedback::stars)
.control(Feedback::stars, Control::Rating)
.submit(t!("feedback.send"), send_feedback)Lookup
Lookup ищет записи на сервере по мере ввода. Действие принимает LookupQuery и возвращает Page<LookupItem>; поле хранит id.
#[action(permission = "stock.product.read")]
pub async fn find_samples(q: LookupQuery, store: SampleStore) -> Result<Page<LookupItem>> {
let items = store.all()?.into_iter().map(|s| LookupItem::new(s.id.0, s.name).hint(s.code)).collect();
Ok(q.apply(items))
}.lookup_many(M::f, action) делает то же для поля Vec<i64> и показывает выбранные значения как теги, которые можно удалить.
.lookup_columns(M::f, action, [t!("code"), t!("name"), t!("city")]) показывает в списке несколько колонок. Поиск по-прежнему идёт в действие на сервере, а его разрешение проверяется при каждом поиске по нажатию клавиши; каждый элемент отдаёт свои значения через LookupItem::cells в порядке заголовков. LookupQuery::apply ищет и по ячейкам. Программы чтения с экрана произносят каждый вариант как "Code: C-2, Name: Mert Kaya, City: Ankara". На узких экранах колонки складываются внутри каждого варианта.
#[action(permission = "sales.customer.read")]
pub async fn find_customers(q: LookupQuery, store: CustomerStore) -> Result<Page<LookupItem>> {
let items = store.all()?.into_iter().map(|c| LookupItem::new(c.id.0, c.name.clone()).cells([c.code, c.name, c.city]));
Ok(q.apply(items.collect()))
}Множественный выбор
Vec из enum с #[choice] редактируется множественным выбором: выбранные варианты показываются как чипы с кнопкой удаления, а combobox открывает список, где варианты включаются и выключаются (APG listbox с aria-multiselectable внутри combobox). Внутри лежит нативный <select multiple>, поэтому поле работает без runtime. Runtime отправляет значения через запятую (Email,Sms); сервер читает их по одному, отклоняет неизвестные значения и убирает повторы. В JSON (API, инструменты AI, база данных) поле это массив значений вариантов.
#[model]
pub struct Contact {
#[rule(required)]
pub channels: Vec<Channel>,
}
Form::of(contact).field(Contact::channels)| Клавиша | Действие |
|---|---|
| Стрелка вниз, стрелка вверх, Enter, пробел | Открывают список на закрытом combobox |
| Home, End | Открывают список на первом или последнем варианте |
| Стрелка вниз, стрелка вверх | Перемещают по открытому списку |
| Shift со стрелкой вниз или вверх | Перемещает и включает или выключает следующий вариант |
| Пробел, Enter | Включают или выключают активный вариант; список остаётся открытым |
| Буквы | Переходят к следующему варианту, который с них начинается |
| Backspace, Delete | На закрытом combobox: удаляют последний выбранный вариант |
| Escape, Alt со стрелкой вверх | Закрывают список; фокус остаётся на combobox |
| Tab | Закрывает список и переводит фокус дальше |
Ctrl+A (выбрать всё в шаблоне APG) оставлен браузеру, как требует политика сочетаний клавиш; кнопки удаления на чипах входят в порядок обхода по Tab. Список, чипы и колонки lookup требуют около 1,7 КБ gzip скрипта и ни одного запроса; поле это 3 узла плюс по одному на каждый выбранный вариант.
Decimal и проценты
Поля Decimal хранят точные числа: нигде между вводом, сервером, базой данных и экспортом нет чисел с плавающей запятой. Поле ввода форматирует число на языке пользователя по мере набора (1.234,5678 по-турецки, 1,234.5678 по-английски) и допускает столько знаков после запятой, сколько задаёт scale поля; сервер читает его на том же языке и заново проверяет scale, precision, min и max.
#[model]
pub struct Terms {
#[rule(precision = 12, scale = 4, min = 0)]
pub rate: Decimal,
#[rule(scale = 1, min = 0, max = 100)]
pub discount: Option<Decimal>,
}
Form::of(terms).field(Terms::rate).field(Terms::discount).control(Terms::discount, Control::Percent)Control::Percent хранит значение в процентных пунктах: 12.5 показывается как 12.5 % по-английски и %12,5 по-турецки, а сохраняется как 12.5. Программы чтения с экрана произносят слово "percent" через описание поля.
Ввод по маске
Текстовое поле с правилом mask показывает свой шаблон во время ввода и отправляет только исходные символы. Шаблон и есть правило: сервер убирает набранные литералы ((532) 123 45 67 становится 5321234567), приводит слоты A к верхнему регистру и отклоняет значение, которое заполняет не каждый слот. Вызовы API и инструменты AI отправляют исходное значение и получают ту же проверку.
#[model]
pub struct Contact {
#[rule(required, mask = "(999) 999 99 99")]
pub phone: String,
#[rule(mask = "AA99 9999 9999 9999 9999 9999 99")]
pub iban: Option<String>,
}| Символ шаблона | Значение |
|---|---|
9 | Цифра |
a | Буква |
A | Буква, записанная в верхнем регистре |
* | Буква или цифра |
| обратная косая черта | Следующий символ это литерал, например \T |
| любой другой | Литерал: показывается, но не сохраняется |
Без runtime поле это обычное текстовое поле с исходным значением; пользователи могут вводить его с литералами или без них.
Пароль
Control::Password (вход), Control::NewPassword (регистрация, смена) и Control::PasswordMeter (новый пароль с индикатором надёжности) это поля пароля, которые никогда не заполняются с сервера и никогда не попадают в черновики. Кнопка показать и скрыть следует шаблону APG toggle button: её имя остаётся "Show password", а aria-pressed сообщает, виден ли текст. При отправке форма снова скрывает пароль.
Индикатор это подсказка, которую браузер вычисляет по длине, видам символов и очевидным шаблонам; он показывается четырьмя полосками и текстом ("Strength: good") в вежливой живой области (polite live region). Что принимает сервер, решают правила поля (min_len, функция validate модели), но никогда не индикатор.
Клавиатура
| Клавиша | Где | Действие |
|---|---|---|
| Стрелка вверх, стрелка вниз | поля decimal и процентов | Шаг в одну целую единицу в пределах min и max |
| Page Up, Page Down | поля decimal и процентов | Шаг в десять |
| Backspace | поле с маской | Удаляет символ перед кареткой; над литералом удаляет символ перед литералом |
| Tab | поле пароля | Переходит к кнопке показать и скрыть |
| Enter, пробел | кнопка показать и скрыть | Показывает или скрывает пароль; фокус остаётся на кнопке |
Бюджет и проверки
| Добавленный скрипт | около 2 КБ gzip (7 КБ исходного) в components.js для масок, переключателя и индикатора; поля decimal и процентов используют числовой элемент |
| Узлы DOM | 3 для поля с маской, 4 для пароля с переключателем, 11 с индикатором |
| Запросы | нет во время ввода |
| Проверки на сервере | scale, precision, min, max и mask выполняются при каждом сохранении, одинаково со страницы, из API и из инструментов AI |
Выбор даты и времени
Элементы выбора принимают ввод в формате пользователя и открывают календарь или список времени. DateOptions задаёт ограничения: .no_weekends(), .week_numbers(), .step_minutes(15). .range(from, to) объединяет два поля даты в один выбор диапазона с готовыми вариантами, например This month. Живой пример находится на странице Время и языки.