Documentation
Introduction
bindForm connects an HTML form to a collection: fields map to document fields by their name,
values are converted with the schema, errors appear next to each field, and
submitting inserts or updates the document. No form library needed.
<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 }]
});
A complete page is in example/form_example.html.
Modes
app.db.users.bindForm('#user'); // insert (default)
app.db.users.bindForm('#user', { mode: 'update', id: 'u1' }); // edit a document
const form = app.db.users.bindForm('#user');
form.load('u1'); // switch to editing u1
- insert:
submitinserts the values; the form is cleared afterwards (resetOnInsert). - update: the document is loaded into the form;
submitwrites only the fields of the form ($setfor values,$unsetfor fields cleared), so fields that are not in the form stay as they are.
Generating the fields from the schema
<form id="signup"></form>
app.db.users.bindForm('#signup', { generate: true });
Builds a labeled field for every property:
- inputs by type: text,
email/urlfor those formats,number(withmin,maxandstep),date, and a checkbox for booleans; a<textarea>for strings withmaxLengthover 120; - a
<select>forenum, a group of checkboxes for arrays ofenum, a comma separated text input for other arrays, and a<fieldset>with a legend for nested objects; minlength,maxlengthandpatternfrom the schema;- labels from
title(or the field name made readable:firstName→ "First name"), a*andaria-requiredon required fields, anddescriptionas a hint linked witharia-describedby.
The fields go in a <div data-sorla-fields> placed before your submit button (or at the end of the
form); you can also put that div where you want them. destroy() removes what was generated.
Value conversion
The form reads text; the schema decides what is stored:
| Schema / input | Stored |
|---|---|
number, integer (or type="number" without a schema) |
a number; text that is not a number stays text so validation reports it |
boolean, or a single checkbox |
true / false |
date |
a Date (<input type="date"> is read as UTC midnight) |
enum |
the enum value whose text matches (so enum: [1, 2] stores numbers) |
array, several checkboxes with the same name, <select multiple> |
an array, items converted with items |
a text input with data-sorla-list or an array schema |
the text split on commas: "a, b" → ['a', 'b'] |
| an empty field | not set ([] for arrays) |
Validation and errors
| Option | Default | |
|---|---|---|
validateOn |
'blur' |
'blur': when the user leaves a field; 'input': while typing; 'submit': only on submit |
nativeValidation |
false |
keep the browser's own bubbles; by default the form gets novalidate so errors look the same everywhere |
messages |
issue => 'text' to translate or rewrite messages |
Before a submit, only the fields the user already left are reported, so a new form is not covered in red. On submit, every problem is shown.
Errors set aria-invalid="true" and aria-describedby on the field and fill the element
[data-error-for="field"]: yours, or a <small class="sorla-error"> created after the field. Errors
that are not about one field (a function schema, a duplicate _id) go to [data-error-for=""] at the
top of the form, with role="alert".
<input name="email" type="email">
<span data-error-for="email" class="hint"></span>
// Portuguese messages
app.db.users.bindForm('#user', {
messages: issue => ({
required: `${issue.path} é obrigatório`,
format: `${issue.path} inválido`
})[issue.rule] ?? issue.message
});
Events and results
form.form.addEventListener('sorla:saved', event => event.detail); // { ok, mode, id, document, modified? }
form.form.addEventListener('sorla:invalid', event => event.detail); // { errors }
onSuccess(result) and onError(errors) receive the same data. modified (update mode) tells whether
anything changed.
The controller
| Member | Description |
|---|---|
values() |
The document the form would save (converted) |
fill(document) |
Puts values into the fields |
validate() |
Shows every error and returns them |
submit() |
Saves; returns { ok: true, mode, id, document, modified? } or { ok: false, errors } |
load(id) |
Loads a document and switches to update mode (throws when the id does not exist) |
reset() |
Resets the form (to the loaded document in update mode, to the schema defaults otherwise) |
destroy() |
Removes the listeners, the error elements and generated fields, restores novalidate |
form, mode, id |
The form element, the current mode and the edited id |
bindForm only uses DOM APIs reachable from the form (form.ownerDocument), so it works inside
iframes and with jsdom in tests.