-
Notifications
You must be signed in to change notification settings - Fork 1
Forms pt BR
🌐 Esta página em: English · Português
Um formulário no eQuantic.UI é um modelo, não um widget. Os valores, as flags, as regras, os
erros e o único submit que pode estar em voo vivem todos em eQuantic.UI.Primitives, lógica pura com
zero dependências e sem pixels, e os componentes por cima só sabem qual dos dois é dono de quê.
Por que uma camada de modelo? Porque um formulário é quase todo aritmética sobre estado: se um erro já pode ser mostrado, se há algo por salvar, se um segundo clique pode submeter de novo. Costure isso num widget e só dá para testar clicando. Mantenha aqui e testa-se afirmando, e o mesmo C# roda no servidor, no browser pelo gêmeo transpilado, e numa janela nativa.
Desde 0.2.0-preview.29
Uma página guarda um FormController, declara os campos dela, e assina uma vez. Todo valor,
flag e erro chega por esse único evento, então nada na página rastreia estado uma segunda vez.
public sealed class SignUpPage : StatefulComponent
{
private readonly FormController _form = new();
public SignUpPage()
{
_form.Add("email", rules: [Rules.Required(), Rules.Email()]);
var password = _form.Add("password", rules: [Rules.Required(), Rules.MinLength(8)]);
_form.Add("confirm", rules: [Rules.Required(), Rules.Matches(password)]);
_form.Changed += () => SetState(() => { });
}
}Add devolve o campo, e é isso que torna possível uma regra entre campos: Rules.Matches(password)
guarda o outro campo e o lê na hora de validar, então julga o que está na tela em vez do que estava
lá quando o formulário foi montado.
Dois pares de propriedades carregam todo o comportamento de um formulário respeitoso, e cada par são duas perguntas diferentes:
| Pergunta | Serve para | |
|---|---|---|
Touched |
o usuário já SAIU deste campo? | se um erro pode ser mostrado |
Dirty |
o valor difere daquele com que o formulário abriu? | "descartar alterações?" |
Error |
o que está errado agora | sempre atual, mesmo antes de alguém digitar |
VisibleError |
o que o campo pode DIZER | ao que um componente se liga |
A consequência é o timing que dá para ver no sample: digite um endereço quebrado e nada fica vermelho, saia do campo e fica, corrija e o vermelho some na tecla que corrige. Um formulário renderizado no servidor chega calado pelo mesmo motivo: toda regra já rodou, e ninguém tocou em nada ainda.
var email = form.Field("email")!;
form.Set("email", "ana@");
email.Error; // "Enter a valid email address.", calculado na hora
email.VisibleError; // null, o cursor ainda está na caixa
form.Touch("email");
email.VisibleError; // "Enter a valid email address."Required, MinLength, MaxLength, Email, Range, Matches, e Custom para aquela que esta
lista não tem. Toda regra menos Required passa num valor vazio: um campo opcional em branco
não está mal formatado, está ausente, e "Digite um e-mail válido" embaixo de uma caixa opcional
vazia é o alarme falso mais comum em validação de formulário.
Uma regra é um predicado mais a mensagem dela, e é isso que a deixa cruzar para o browser:
System.ComponentModel.DataAnnotations valida por reflexão, e reflexão é exatamente o que o
transpilador não consegue levar.
form.Add("age", rules: [Rules.Range(18, 120, "You must be 18 or older.")]);
form.Add("slug", rules: [Rules.Custom("Lowercase letters and dashes only.",
value => value.All(c => char.IsAsciiLetterLower(c) || c == '-'))]);Desde 0.2.0-preview.29
Formulário de verdade tem mais de um caminho por dentro, e a validação condicional chega como duas peças que se compõem, não como um segundo motor.
Rules.When torna qualquer regra condicional: ela envolve, então toda regra acima já é
condicional de graça. Enquanto a condição é falsa a regra vale vacuamente: nada foi pedido, então
o campo se cala em vez de guardar a última resposta que deu.
relevantWhen desliga o CAMPO inteiro. Um endereço de entrega num pedido que vai ser retirado na
loja não é um campo com regra que passa, é um campo que ninguém está perguntando: não guarda erro,
não pode invalidar o formulário, e a página pode simplesmente não desenhá-lo. O que foi digitado
sobrevive à pergunta sumir e voltar.
var kind = _form.Add("kind", "personal");
// A regra vai e volta; o campo fica.
_form.Add("taxNumber", rules: [Rules.When(() => kind.Value == "company", Rules.Required())]);
// O campo em si não está sendo perguntado enquanto a caixa está desmarcada.
_form.Add("phone", relevantWhen: () => _callMe,
rules: [Rules.Required("We need a number to call you on."), Rules.MinLength(9)]);
// …e a página o desenha só enquanto ele se aplica:
if (_form.Field("phone") is { Relevant: true })
card.Add(new FormInput(_form, "phone", "Phone"));Uma condição lê o que ela capturar. Quando isso é outro campo, nada mais é preciso: mudar
qualquer valor re-roda as regras de todos os outros campos. Quando é estado que o formulário não
possui (um checkbox na página, um plano escolhido num passo anterior), a página avisa com
Revalidate():
card.Add(new Checkbox(_callMe, () => SetState(() =>
{
_callMe = !_callMe;
_form.Revalidate(); // a condição mora fora do formulário, então o formulário é avisado
}), "Call me instead of emailing"));Desde 0.2.0-preview.29
Dois componentes ligam o modelo aos pixels, e são finos de propósito. FormInput tem três fios e
mais nada: digitar chama Set, sair chama Touch, e o que ele desenha é VisibleError.
FormSubmit lê do controller todo o resto.
card.Add(new FormInput(_form, "email", "Email", placeholder: "you@example.com",
helper: "We never share it."));
card.Add(new FormInput(_form, "password", "Password", helper: "At least 8 characters")
{ Obscure = true });
var actions = new Row(gap: Space.S2);
actions.Add(new FormSubmit(_form, "Create account", Submit));
actions.Add(new Button("Reset", Variant.Ghost) { OnPressed = () => _form.Reset() });O botão de submit continua clicável enquanto o formulário é inválido, e essa escolha merece
defesa: um submit desabilitado é a forma mais comum de fazer um formulário parecer quebrado, porque
o campo que está errado costuma ser um que o usuário nunca visitou. Apertar é o que revela a
resposta, e o SubmitAsync toca em todos os campos primeiro.
Desde 0.2.0-preview.29
Um modelo anotado com System.ComponentModel.DataAnnotations já descreve quase todo o formulário.
Marque com [FormModel] e o build escreve o controller:
[FormModel]
public sealed class SignUp
{
[Required, EmailAddress] public string Email { get; set; } = "";
[Required, MinLength(8)] public string Password { get; set; } = "";
[Required, Compare(nameof(Password))] public string Confirm { get; set; } = "";
}
private readonly FormController _form = SignUpForm.Create(); // geradoA leitura acontece em tempo de build, e tem que ser: DataAnnotations valida por REFLEXÃO, que é
exatamente o que o transpilador não leva para o browser. O que é emitido são chamadas ORDINÁRIAS
para as mesmas Rules acima, então não existe um segundo motor de validação, então o formulário gerado e
um escrito à mão são o mesmo objeto. Um modelo adota a ponte sem a página mudar, e um formulário
cresce além dela sem reescrita:
public FormScreen()
{
// …o campo que nenhuma anotação descreve, adicionado ao formulário que o gerador construiu.
_form.Add("Phone", relevantWhen: () => _callMe, rules: [Rules.Required(), Rules.MinLength(9)]);
}Os campos têm o nome da PROPRIEDADE ("Email"), que é também o que o model state do servidor
reporta, então o ApplyServerErrors cai no campo certo sem nada no meio.
Levados: Required, EmailAddress, MinLength, MaxLength, StringLength (os dois limites),
Range, RegularExpression e Compare. O ErrorMessage ganha da mensagem da própria regra,
porque um app que escreveu uma escreveu para ser mostrada.
Ditos em voz alta em vez de descartados: um tipo de propriedade que nenhuma caixa de texto
transporta (EQ3103), uma anotação sem regra correspondente (EQ3104), um [Compare] apontando
para uma propriedade que não existe (EQ3105). Todos avisos: um formulário que carrega a maior
parte de um modelo ainda vale a pena, e o servidor aplica o resto de qualquer jeito. É opt-in,
porque um modelo anotado para uma API não é automaticamente um formulário.
SubmitAsync dá ao chamador três garantias que ele reimplementaria em toda página: um formulário
inválido nunca chega ao handler (em vez disso todo erro é revelado), um segundo submit é recusado
enquanto o primeiro está em voo (o clique duplo que cobra o cartão duas vezes), e um throw vira
SubmitError em vez de exceção não tratada, porque a rede falhar é coisa que acontece com formulário, não
um crash.
As regras do cliente são uma cortesia. A validação que conta roda onde os dados moram, e
ApplyServerErrors é como a resposta dela volta para o campo a que pertence:
[ServerAction]
public async Task<List<FieldError>> Register(string email) =>
await _users.Exists(email)
? [new FieldError("email", "That address is already registered.")]
: [];
private async Task Submit()
{
var verdict = await Register(_form.Field("email")!.Value);
if (verdict.Count > 0) { _form.ApplyServerErrors(verdict); return; }
_form.Accept(); // os valores atuais viram a nova base: nada sujo, nada gritando
}-
Datas, enums e o que mais uma caixa de texto não transporta ficam fora da ponte de
DataAnnotations: são reportados (
EQ3103) e deixados para um campo escrito à mão, porque uma data precisa de um seletor e de uma cultura antes de precisar de uma regra. - Mensagens são strings simples, não chaves de recurso. Um app que localiza as mensagens dele passa strings já localizadas, porque o resx é dele. Veja Localização para o chrome do próprio SDK, que é o único texto que este framework traduz por você.
- Componentes write-once: como uma única biblioteca de componentes alcança o browser e uma janela nativa.
- Design System: o input, o checkbox e o botão de que a superfície é feita.
-
Integração com o servidor:
[ServerAction], que é como um formulário alcança o servidor.
🌐 English · Português
🏁 Comece aqui
📱 Write-once
- Componentes write-once
- Superfície declarativa
- Motor Photon
- Design System
- Capacidades
- Armazenamento
- Formulários
- Editor de código
- Markdown
- Mermaid
- Renderização de Email
🏗️ Arquitetura
⚙️ Compilação
- Compilador
- Avaliação em tempo de compilação
- Recursos C# suportados
- Resolução de tipos externos
- Fluxo de build
- Diagnósticos
⚡ Runtime
🔌 Servidor
🎨 Ecossistema
🚀 Desenvolvimento