Skip to content

Forms pt BR

Edgar Mesquita edited this page Aug 13, 2026 · 4 revisions

Formulários

🌐 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, zero dependências, 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.


Declarando um formulário

Since 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.


Calado até você sair do campo

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."

As regras

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 == '-'))]);

Um formulário que muda de forma

Since 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"));

A superfície

Since 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 — SubmitAsync toca em todos os campos primeiro.


Submeter, e o veredito do servidor

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 — 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
}

Cercas

  • Ainda não há ponte de [Required] / [EmailAddress]. Os atributos que um modelo já carrega vão ser lidos em tempo de BUILD e emitidos como chamadas para estas mesmas regras — até lá, as regras são escritas à mão. De propósito não existe um segundo motor de validação para o gerador mirar.
  • 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ê.

Relacionados

Clone this wiki locally