# Forms
- [Introduction](#introduction)
- [MagicFormData](#magicformdata)
- [Creating Form Data](#creating-form-data)
- [Accessing Values](#accessing-values)
- [Setting Values](#setting-values)
- [MagicForm Widget](#magicform-widget)
- [Form Inputs](#form-inputs)
- [WFormInput](#wforminput)
- [WFormCheckbox](#wformcheckbox)
- [WFormSelect](#wformselect)
- [Form Validation](#form-validation)
- [Submitting Forms](#submitting-forms)
- [Complete Example](#complete-example)
## Introduction
Magic provides a powerful form handling system that combines the simplicity of Laravel's request handling with Flutter's form widgets. Forms are managed through `MagicFormData`, which centralizes form state, validation, and data extraction.
## MagicFormData
### Creating Form Data
Create a form data instance in your stateful view:
```dart
class LoginView extends MagicStatefulView {
const LoginView({super.key});
@override
State createState() => _LoginViewState();
}
class _LoginViewState extends MagicStatefulViewState {
// Define form fields with initial values
// String values create TextEditingControllers
// bool values create ValueNotifiers
late final form = MagicFormData({
'email': '',
'password': '',
'remember_me': false,
}, controller: controller);
@override
void onClose() => form.dispose(); // Always dispose
}
```
### Accessing Values
```dart
// Get string value
String email = form.get('email');
// Get typed value
bool? rememberMe = form.value('remember_me');
int? age = form.value('age');
// Get TextEditingController for text fields
TextEditingController emailController = form['email'];
```
### Setting Values
```dart
// Set any value
form.setValue('email', 'john@example.com');
form.setValue('remember_me', true);
form.setValue('tags', ['flutter', 'magic']);
// Reset form to initial values
form.reset();
```
## MagicForm Widget
Wrap your form fields with `MagicForm` for automatic state management:
```dart
@override
Widget build(BuildContext context) {
return MagicForm(
formData: form,
child: WDiv(
className: 'flex flex-col gap-4',
children: [
WFormInput(
controller: form['email'],
label: trans('attributes.email'),
validator: rules([Required(), Email()], field: 'email'),
),
WFormInput(
controller: form['password'],
type: InputType.password,
label: trans('attributes.password'),
validator: rules([Required(), Min(8)], field: 'password'),
),
WButton(
onTap: _submit,
child: WText(trans('auth.login')),
),
],
),
);
}
```
`MagicForm` provides:
- Form key for validation
- Auto-validation mode when errors exist
- Automatic error state binding
- Server-side error display
## Form Inputs
### WFormInput
The primary text input widget with Wind UI styling:
```dart
WFormInput(
controller: form['email'],
label: trans('attributes.email'),
placeholder: trans('fields.email_placeholder'),
type: InputType.email, // text, password, email, number, multiline
validator: rules([Required(), Email()], field: 'email'),
className: '''
w-full bg-slate-900 border border-gray-700 rounded-lg p-3 text-white
focus:border-primary focus:ring-2 focus:ring-primary/30
error:border-red-500 error:ring-red-200
''',
labelClassName: 'text-sm font-medium text-gray-300 mb-1',
placeholderClassName: 'text-gray-400',
)
```
#### Input Types
| Type | Description |
|------|-------------|
| `InputType.text` | Standard text input (default) |
| `InputType.password` | Obscured password input |
| `InputType.email` | Email keyboard |
| `InputType.number` | Numeric keyboard |
| `InputType.phone` | Phone number keyboard |
| `InputType.multiline` | Multi-line text area |
### WFormCheckbox
Checkbox with validation support:
```dart
WFormCheckbox(
value: form.value('accept_terms'),
onChanged: (v) => form.setValue('accept_terms', v),
label: WText(trans('auth.accept_terms')),
validator: rules([Accepted()], field: 'accept_terms'),
className: 'w-5 h-5 checked:bg-primary error:border-red-500',
)
```
### WFormSelect
Dropdown select with search support:
```dart
WFormSelect(
value: form.get('country'),
options: [
SelectOption(value: 'us', label: 'United States'),
SelectOption(value: 'gb', label: 'United Kingdom'),
SelectOption(value: 'tr', label: 'Turkey'),
],
onChange: (v) => form.setValue('country', v),
label: trans('attributes.country'),
searchable: true,
placeholder: trans('fields.select_country'),
validator: rules([Required()], field: 'country'),
className: 'w-full bg-slate-900 border border-gray-700 rounded-lg',
)
```
## Form Validation
### Client-Side Validation
Use the `rules()` helper for client-side validation:
```dart
WFormInput(
controller: form['email'],
validator: rules([Required(), Email()], field: 'email'),
)
WFormInput(
controller: form['password_confirmation'],
validator: rules([
Required(),
Same('password', valueGetter: () => form['password'].text),
], field: 'password_confirmation'),
)
```
### Server-Side Errors
Server-side validation errors are automatically displayed when using `ValidatesRequests` mixin in your controller:
```dart
class AuthController extends MagicController with ValidatesRequests {
Future register(Map data) async {
clearErrors(); // Clear previous errors
final response = await Http.post('/register', data: data);
if (!response.successful) {
handleApiError(response); // Auto-populates field errors
}
}
}
```
## Submitting Forms
### Using validated()
The `validated()` method validates the form and returns data if valid:
```dart
void _submit() {
final data = form.validated();
if (data.isNotEmpty) {
// Form is valid, submit to controller
controller.register(data);
}
// If empty, validation failed (errors are shown automatically)
}
```
### Using validate()
For more control, use `validate()` separately:
```dart
void _submit() {
if (form.validate()) {
// Form is valid
final data = {
'email': form.get('email'),
'password': form.get('password'),
'remember_me': form.value('remember_me'),
};
controller.login(data);
}
}
```
## Complete Example
```dart
class RegisterView extends MagicStatefulView {
const RegisterView({super.key});
@override
State createState() => _RegisterViewState();
}
class _RegisterViewState extends MagicStatefulViewState {
late final form = MagicFormData({
'name': '',
'email': '',
'password': '',
'password_confirmation': '',
'accept_terms': false,
}, controller: controller);
@override
void onClose() => form.dispose();
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (context, _) => _buildForm(),
);
}
Widget _buildForm() {
return WDiv(
className: 'max-w-md mx-auto p-6 bg-slate-900 rounded-xl',
child: MagicForm(
formData: form,
child: WDiv(
className: 'flex flex-col gap-4',
children: [
WText(trans('auth.register_title'),
className: 'text-2xl font-bold text-white text-center'),
WFormInput(
controller: form['name'],
label: trans('attributes.name'),
validator: rules([Required(), Min(2)], field: 'name'),
className: _inputClass,
),
WFormInput(
controller: form['email'],
type: InputType.email,
label: trans('attributes.email'),
validator: rules([Required(), Email()], field: 'email'),
className: _inputClass,
),
WFormInput(
controller: form['password'],
type: InputType.password,
label: trans('attributes.password'),
validator: rules([Required(), Min(8)], field: 'password'),
className: _inputClass,
),
WFormInput(
controller: form['password_confirmation'],
type: InputType.password,
label: trans('attributes.password_confirmation'),
validator: rules([
Required(),
Same('password', valueGetter: () => form['password'].text),
], field: 'password_confirmation'),
className: _inputClass,
),
WFormCheckbox(
value: form.value('accept_terms'),
onChanged: (v) => setState(() => form.setValue('accept_terms', v)),
label: WText(trans('auth.accept_terms'), className: 'text-gray-300'),
validator: rules([Accepted()], field: 'accept_terms'),
),
SizedBox(height: 8),
WButton(
isLoading: controller.isLoading,
onTap: _submit,
className: 'w-full bg-primary p-4 rounded-lg',
child: WText(trans('auth.register'), className: 'text-white text-center'),
),
],
),
),
);
}
String get _inputClass => '''
w-full bg-slate-800 border border-gray-700 rounded-lg p-3 text-white
focus:border-primary error:border-red-500
''';
void _submit() {
final data = form.validated();
if (data.isNotEmpty) {
controller.register(data);
}
}
}
```