Geavanceerde formulieren
AdvancedForm is het ingebouwde formuliervalidatie- en rendersysteem van Finch. Het combineert veldbepalingen, validators, CSRF-beveiliging en sjabloonrendering in één klasse.
Een formulier definiëren
Breid AdvancedForm uit en override name, widget en fields():
import 'package:finch/finch_ui.dart';
class PersonForm extends AdvancedForm {
@override
String get name => 'form_person';
@override
String get widget => 'forms/person.j2.html';
@override
List<Field> fields() {
return [
csrf(), // CSRF-beveiligingstoken (altijd toevoegen)
Field('name', validators: [
FieldValidator.requiredField(),
FieldValidator.fieldLength(min: 3, max: 100),
]),
Field('email', validators: [
FieldValidator.requiredField(),
FieldValidator.isEmailField(),
]),
Field('age', validators: [
FieldValidator.isNumberField(isRequired: false),
]),
];
}
}
Field(name, {validators, initValue, type, initOptions}) — initValue vult het veld vooraf in (bijv. een standaarddatum), en initOptions is een asynchrone callback die wordt gebruikt bij select-achtige velden om hun optielijst te vullen (zie het categorievoorbeeld hieronder).
Ingebouwde validators
FieldValidator behandelt zowel eenvoudige waardecontroles als MongoDB/SQL-relatie- en uniekheidscontroles:
| Validator | Beschrijving |
|---|---|
FieldValidator.requiredField() |
Veld mag niet leeg zijn |
FieldValidator.requiredFieldMultiLanguage() |
Verplicht, verwacht een JSON-object met waarden per taal (ten minste één niet-leeg) |
FieldValidator.fieldLength({min, max}) |
Bereik voor stringlengte |
FieldValidator.isNumberField({min, max, isRequired}) |
Moet een geheel getal zijn, optioneel begrensd (isRequired is standaard false) |
FieldValidator.isNumberDoubleField({min, max, isRequired}) |
Hetzelfde als hierboven, voor decimale getallen |
FieldValidator.isEmailField() |
Moet een geldig e-mailadres zijn |
FieldValidator.isPasswordField() |
Ten minste 8 tekens, met een hoofdletter, kleine letter, cijfer en speciaal teken |
FieldValidator.isColorField() |
Moet een hexadecimale kleur zijn (#fff of #ffffff) |
FieldValidator.isSelectField(options) |
Waarde moet één van de opgegeven List met opties zijn |
FieldValidator.isDateField({isRequired, checkUtc}) |
Moet een parseerbare datum zijn; checkUtc vereist bovendien dat deze UTC is |
FieldValidator.contains(values, {isRequired}) |
Waarde moet één van values zijn |
FieldValidator.hasRelation({collectionModel, relationField, isRequired}) |
MongoDB: waarde moet verwijzen naar de _id van een bestaand document in collectionModel (een DBCollectionFree) |
FieldValidator.hasSqlRelation({db, table, field, isRequired, operator, where}) |
SQL: waarde moet bestaan als field in table — zie MySQL/SQLite voor db |
FieldValidator.isUniqueSQLField({db, table, field, operator, where}) |
SQL: waarde mag niet al bestaan als field in table |
FieldValidator.checkByRegexp(pattern, {isRequired}) |
Aangepaste RegExp-controle — niet regExp() |
Een select-achtig veld met dynamisch geladen opties (aangepast van het boekformulier uit het voorbeeldproject, dat categorieën laadt vanuit MySQL of SQLite):
Field(
'category_id',
validators: [
FieldValidator.hasSqlRelation(
isRequired: false,
db: app.mysqlDriver,
table: 'categories',
field: 'id',
),
],
initOptions: (field) async {
var categories = await CategoriesRepository(app.mysqlDriver).getAllCategories();
return categories.rows.assoc; // de geretourneerde lijst wordt de `options` van dit veld
},
),
Het formulier gebruiken in een controller
Future<String> personForm() async {
var form = PersonForm();
return form.check(
onValid: (formData) async {
// form.get<String>('name') retourneert de gevalideerde waarde nadat check() is uitgevoerd
var name = form.get<String>('name');
var email = form.get<String>('email');
// verwerk gegevens...
return rq.redirect('/example/person');
},
onInvalid: (formData) async {
// formulierstatus (waarden + fouten) is beschikbaar in het sjabloon via rq-params
return rq.renderView(path: 'example/person');
},
);
}
rq binnen AdvancedForm is al Context.rq — er is geen aparte stap nodig om het huidige verzoek te koppelen. onValid/onInvalid ontvangen beide de data-map van het gecontroleerde formulier als hun enige argument (ook als je die niet gebruikt) — een callback die zonder parameters wordt gedeclareerd, compileert wel (het veld is los getypeerd als Function), maar crasht tijdens runtime wanneer check() deze aanroept, omdat er in werkelijkheid met één argument wordt aangeroepen. Bij een GET-verzoek roept form.check() altijd onInvalid aan, zodat het lege formulier wordt getoond.
De formulierwidget opnemen in een sjabloon
Voeg de formulierwidget toe aan een bovenliggend sjabloon:
{% include form_person.widget | unscape %}
Het pad van de formulierwidget is de widget-eigenschap van je klasse.
Veldstatus lezen in sjablonen
De waarde en fouten van elk veld zijn beschikbaar via $n('formName/fieldName/...'). Het CSRF-veld wordt toegevoegd in fields() via csrf(), maar de werkelijke veldnaam is token (de csrfTokenName van de klasse, standaard 'token') — niet csrf:
<form method="POST" action="/example/person">
<input
type="text"
name="name"
value="{{ $n('form_person/name/value') }}"
class="{{ 'border-red-500' if $n('form_person/name/errors/0') else '' }}"
/>
<p class="text-red-600">{{ $n('form_person/name/errors/0') }}</p>
<input
type="email"
name="email"
value="{{ $n('form_person/email/value') }}"
/>
<p class="text-red-600">{{ $n('form_person/email/errors/0') }}</p>
<!-- CSRF-token -->
<input type="hidden" name="token" value="{{ $n('form_person/token/value') }}" />
<button type="submit">Save</button>
</form>
Gedrag van het API-eindpunt
Wanneer de verzoek-URL begint met /api/, retourneert AdvancedForm een JSON-respons in plaats van de widget te renderen:
{
"form_person": {
"name": { "value": "Alice", "errors": [] },
"email": { "value": "", "errors": ["Email is required"] }
}
}
CSRF-beveiliging
Door csrf() op te nemen in fields() wordt een verborgen CSRF-tokenveld toegevoegd (genaamd token). Het token wordt per formuliernaam gegenereerd, opgeslagen in de sessie en automatisch gevalideerd bij POST/PUT — hetzelfde token wordt tot 6 uur hergebruikt voordat het wordt vernieuwd. Er hoeft verder niets te worden aangeroepen om dit in te schakelen, behalve het opnemen van csrf() in je fields()-lijst en het renderen van de verborgen token-input zoals hierboven getoond.