Advanced Forms
AdvancedForm is Finch's built-in form validation and rendering system. It ties field definitions, validators, CSRF protection, and template rendering together in one class.
Defining a Form
Extend AdvancedForm, override name, widget, and 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 protection token (always include)
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),
]),
];
}
}
Built-in Validators
| Validator | Description |
|---|---|
FieldValidator.requiredField() |
Field must not be empty |
FieldValidator.isEmailField() |
Must be a valid email |
FieldValidator.isPasswordField() |
Password strength check |
FieldValidator.fieldLength(min, max) |
String length range |
FieldValidator.isNumberField(isRequired) |
Must be numeric |
FieldValidator.isDateField(isRequired) |
Must be a valid date |
FieldValidator.regExp(pattern) |
Custom regex check |
Using the Form in a Controller
Future<String> personForm() async {
var form = PersonForm();
form.setRq(rq); // inject current request
return form.check(
onValid: () async {
// form.getValue('name') returns the validated value
var name = form.getValue('name');
var email = form.getValue('email');
// process data...
return rq.redirect('/example/person');
},
onInvalid: () async {
// form state (values + errors) is available in the template via rq params
return rq.renderView(path: 'example/person');
},
);
}
On a GET request form.check() always calls onInvalid so the blank form is shown.
Including the Form Widget in a Template
Add the form widget to a parent template:
{% include form_person.widget | unscape %}
The form widget path is the widget property of your class.
Reading Field State in Templates
Each field's value and errors are available via $n('formName/fieldName/...'):
<form method="POST" action="/example/person">
{{ form_person.csrf | safe }}
<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>
<button type="submit">Save</button>
</form>
API Endpoint Behaviour
When the request URL starts with /api/, AdvancedForm returns a JSON response instead of rendering the widget:
{
"form_person": {
"name": { "value": "Alice", "errors": [] },
"email": { "value": "", "errors": ["Email is required"] }
}
}
CSRF Protection
Including csrf() in fields() adds a hidden CSRF token field. The token is automatically validated on POST. Output it in HTML:
{{ form_person.csrf | safe }}
What are Advanced Forms?
Advanced forms in Finch are a way to create complex forms with validation and error handling. They are based on the AdvancedForm class. This class provides a simple way to create forms with validation and error handling. You can use this class to create forms with multiple fields, each with its own validation rules.
It reads the data from the request and validates it against the validation rules. It also provides a way to render the form in the view. You can use this class to create forms for user registration, login, profile update, etc.
Using Advanced Forms
To use advanced forms in your Finch application, you need to create a class that extends the AdvancedForm class. This class should implement the fields method, which returns a list of Field objects. Each Field object represents a field in the form and its validation rules.
Here is an example of how to create an advanced form for user registration:
class UserRegistrationForm extends AdvancedForm {
@override
String get widget => 'forms/registration.j2.html';
@override
String get name => 'form_example';
@override
List<Field> fields() {
return [
csrf(),
Field('name', validators: [
FieldValidator.requiredField(),
FieldValidator.fieldLength(min: 3),
]),
Field('email', validators: [
FieldValidator.requiredField(),
FieldValidator.isEmailField(),
]),
Field('password', validators: [
FieldValidator.requiredField(),
FieldValidator.isPasswordField(),
]),
];
}
}
In the above example, we have created a form with three fields: name, email, and password. Each field has its own validation rules. The csrf() method is used to add a CSRF token to the form. This is a security measure to prevent cross-site request forgery attacks. The csrf() method is provided by the AdvancedForm class.
Rendering the Form
To render the form in the view, you can use the render method provided by the AdvancedForm class. This method returns a string that can be rendered in the view. You can use this string in your view to render the form.
var form = UserRegistrationForm();
var formHtml = await form.render();
rq.addParam('form', formHtml);
return rq.renderView(path: 'user/registration.j2.html');
In the above example, we have created an instance of the UserRegistrationForm class. We have then called the render method to get the HTML string of the form. We have added this string to the request parameters with the key form. Finally, we have rendered the view user/registration.j2.html which will render the form.
Validating the Form
To validate the form, you can use the check method provided by the AdvancedForm class. This method validates the form data against the validation rules and returns a boolean value. If the form is valid, it returns true. Otherwise, it returns false.
var form = UserRegistrationForm();
var isValid = await form.check();
if (isValid) {
// Process valid form data
} else {
// Handle validation errors
}
In the above example, we have created an instance of the UserRegistrationForm class. We have then called the check method to validate the form data. If the form is valid, we can process the form data. Otherwise, we can handle the validation errors.
API Response
This kind as form would return a json response with the form data and validation errors. You can use this data to render the form in the view. You can also use this data to handle the validation errors in the view. Advanced form dettects API requests through /api/ in the url and it will return json response.
Render form in view
to render the form in the view, you can use the following code in your template:
{% include form_example.widget | unscape %}
Template Example
<form method="POST" action="/example/person/{{ (data._id) if data._id else '' }}" class="space-y-8">
<input
type="text"
name="name"
required
value="{{ $n('form_example/name/value') }}"
class="{{ 'border-rose-500 ring-2' if $n('form_example/name/errors/0') else 'border-slate-300' }}"
/>
<div class="mt-1 text-[10px] text-rose-600 {{ '' if $n('form_example/name/errors/0') else 'hidden' }}">{{ $n('form_example/name/errors/0') }}</div>
<input
type="email"
name="email"
required
value="{{ $n('form_example/email/value') }}"
class="{{ 'border-rose-500 ring-2' if $n('form_example/email/errors/0') else 'border-slate-300' }}"
/>
<div class="mt-1 text-[10px] text-rose-600 {{ '' if $n('form_example/email/errors/0') else 'hidden' }}">{{ $n('form_example/email/errors/0') }}</div>
<input
type="password"
name="password"
required
value="{{ $n('form_example/password/value') }}"
class="{{ 'border-rose-500 ring-2' if $n('form_example/password/errors/0') else 'border-slate-300' }}"
/>
<div class="mt-1 text-[10px] text-rose-600 {{ '' if $n('form_example/password/errors/0') else 'hidden' }}">{{ $n('form_example/password/errors/0') }}</div>
<!-- CSRF Token -->
<input type="hidden" name="token" value="{{ $n('form_example/token/value') }}" />
<div class="mt-1 text-[10px] text-rose-600 {{ $n('form_example/token/errors/0') ? '' : 'hidden' }}">{{ $n('form_example/token/errors/0') }}</div>
<button type="submit" class="wave inline-flex h-9 items-center rounded-md border border-primary-600 bg-primary-50 px-3 text-xs font-medium text-primary-700 hover:bg-primary-100 focus:outline-none focus:ring-2 focus:ring-primary-500/30">{{ $t('database.table.button.add') }}</button>
</form>