Documentação
Introdução
bindForm conecta um formulário HTML a uma coleção: os campos do formulário são mapeados para os
campos do documento pelo name, os valores são convertidos de acordo com o schema,
os erros aparecem ao lado de cada campo e o envio insere ou atualiza o documento. Nenhuma biblioteca de
formulários é necessária.
<form id="user">
<label>Name <input name="name"></label>
<label>Email <input name="email" type="email"></label>
<label>Age <input name="age" type="number"></label>
<label>ZIP <input name="address.zip"></label>
<label><input type="checkbox" name="tags" value="dev"> Dev</label>
<label><input type="checkbox" name="tags" value="ops"> Ops</label>
<button>Save</button>
</form>
const form = app.db.users.bindForm('#user', {
onSuccess: result => console.log(result.mode, result.id), // 'insert', '…'
onError: errors => console.log(errors) // [{ path, rule, message }]
});
Uma página completa está em example/form_example.html.
Modos
app.db.users.bindForm('#user'); // insert (padrão)
app.db.users.bindForm('#user', { mode: 'update', id: 'u1' }); // edita um documento
const form = app.db.users.bindForm('#user');
form.load('u1'); // passa a editar u1
- insert:
submitinsere os valores; depois disso o formulário é limpo (resetOnInsert). - update: o documento é carregado no formulário;
submitgrava apenas os campos do formulário ($setpara os valores,$unsetpara os campos que foram esvaziados), então os campos que não estão no formulário ficam como estão.
Gerando os campos a partir do schema
<form id="signup"></form>
app.db.users.bindForm('#signup', { generate: true });
Cria um campo com rótulo para cada propriedade:
- inputs conforme o tipo: texto,
email/urlpara esses formatos,number(commin,maxestep),date, e um checkbox para booleanos; um<textarea>para strings commaxLengthacima de 120; - um
<select>paraenum, um grupo de checkboxes para arrays deenum, um input de texto separado por vírgulas para os demais arrays, e um<fieldset>com legenda para objetos aninhados; minlength,maxlengthepatternvindos do schema;- rótulos a partir do
title(ou do nome do campo em forma legível:firstName→ "First name"), um*earia-requirednos campos obrigatórios, e adescriptioncomo dica vinculada comaria-describedby.
Os campos vão para uma <div data-sorla-fields> colocada antes do seu botão de envio (ou no final do
formulário); você também pode colocar essa div onde quiser que eles apareçam. destroy() remove o que
foi gerado.
Conversão de valores
O formulário lê texto; o schema decide o que é armazenado:
| Schema / input | Armazenado |
|---|---|
number, integer (ou type="number" sem schema) |
um número; um texto que não é número continua texto, para que a validação o aponte |
boolean, ou um único checkbox |
true / false |
date |
um Date (<input type="date"> é lido como meia-noite UTC) |
enum |
o valor do enum cujo texto corresponde (assim, enum: [1, 2] armazena números) |
array, vários checkboxes com o mesmo nome, <select multiple> |
um array, com os itens convertidos conforme items |
um input de texto com data-sorla-list ou com schema array |
o texto dividido nas vírgulas: "a, b" → ['a', 'b'] |
| um campo vazio | não definido ([] para arrays) |
Validação e erros
| Opção | Padrão | |
|---|---|---|
validateOn |
'blur' |
'blur': quando o usuário sai de um campo; 'input': durante a digitação; 'submit': apenas no envio |
nativeValidation |
false |
mantém os balões do próprio navegador; por padrão o formulário recebe novalidate, para que os erros tenham a mesma aparência em todo lugar |
messages |
issue => 'text' para traduzir ou reescrever as mensagens |
Antes de um envio, só são apontados os campos dos quais o usuário já saiu, para que um formulário novo não apareça todo em vermelho. No envio, todos os problemas são exibidos.
Os erros definem aria-invalid="true" e aria-describedby no campo e preenchem o elemento
[data-error-for="field"]: o seu, ou um <small class="sorla-error"> criado logo após o campo. Erros
que não se referem a um campo específico (um schema em forma de função, um _id duplicado) vão para
[data-error-for=""] no topo do formulário, com role="alert".
<input name="email" type="email">
<span data-error-for="email" class="hint"></span>
// mensagens em português
app.db.users.bindForm('#user', {
messages: issue => ({
required: `${issue.path} é obrigatório`,
format: `${issue.path} inválido`
})[issue.rule] ?? issue.message
});
Eventos e resultados
form.form.addEventListener('sorla:saved', event => event.detail); // { ok, mode, id, document, modified? }
form.form.addEventListener('sorla:invalid', event => event.detail); // { errors }
onSuccess(result) e onError(errors) recebem os mesmos dados. modified (modo update) indica se
alguma coisa mudou.
O controlador
| Membro | Descrição |
|---|---|
values() |
O documento que o formulário salvaria (já convertido) |
fill(document) |
Coloca valores nos campos |
validate() |
Mostra todos os erros e os retorna |
submit() |
Salva; retorna { ok: true, mode, id, document, modified? } ou { ok: false, errors } |
load(id) |
Carrega um documento e passa para o modo update (lança um erro quando o id não existe) |
reset() |
Restaura o formulário (para o documento carregado no modo update; caso contrário, para os valores padrão do schema) |
destroy() |
Remove os ouvintes, os elementos de erro e os campos gerados, e restaura o novalidate |
form, mode, id |
O elemento do formulário, o modo atual e o id em edição |
bindForm usa apenas APIs do DOM acessíveis a partir do formulário (form.ownerDocument), então funciona
dentro de iframes e com o jsdom nos testes.