Form
Introduzione
Nylo Website v7 fornisce un sistema di form costruito attorno a NyFormWidget. La tua classe form estende NyFormWidget ed e' il widget -- non serve un wrapper separato. I form supportano validazione integrata, molti tipi di campo, stile e gestione dei dati.
import 'package:nylo_framework/nylo_framework.dart';
// 1. Define a form
class LoginForm extends NyFormWidget {
LoginForm({super.key, super.submitButton, super.onSubmit, super.onFailure});
@override
fields() => [
Field.email("Email", validator: FormValidator.email()),
Field.password("Password", validator: FormValidator.password()),
];
static NyFormActions get actions => const NyFormActions('LoginForm');
}
// 2. Display and submit it
LoginForm(
submitButton: Button.primary(text: "Login"),
onSubmit: (data) {
print(data); // {email: "...", password: "..."}
},
)
Creare un Form
Usa la CLI Metro per creare un nuovo form:
metro make:form LoginForm
Questo crea lib/app/forms/login_form.dart:
import 'package:nylo_framework/nylo_framework.dart';
class LoginForm extends NyFormWidget {
LoginForm({super.key, super.submitButton, super.onSubmit, super.onFailure});
@override
fields() => [
Field.email("Email", validator: FormValidator.email()),
Field.password("Password", validator: FormValidator.password()),
];
static NyFormActions get actions => const NyFormActions('LoginForm');
}
I form estendono NyFormWidget e sovrascrivono il metodo fields() per definire i campi del form. Ogni campo usa un costruttore nominato come Field.text(), Field.email() o Field.password(). Il getter static NyFormActions get actions fornisce un modo conveniente per interagire con il form da qualsiasi punto della tua app.
Visualizzare un Form
Poiche' la tua classe form estende NyFormWidget, e' il widget. Usalo direttamente nel tuo albero di widget:
@override
Widget view(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LoginForm(
submitButton: Button.primary(text: "Submit"),
onSubmit: (data) {
print(data);
},
),
),
);
}
Inviare un Form
Ci sono tre modi per inviare un form:
Utilizzando onSubmit e submitButton
Passa onSubmit e un submitButton quando costruisci il form. Nylo Website fornisce pulsanti predefiniti che funzionano come pulsanti di invio:
LoginForm(
submitButton: Button.primary(text: "Submit"),
onSubmit: (data) {
print(data); // {email: "...", password: "..."}
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
)
Stili di pulsante disponibili: Button.primary, Button.secondary, Button.outlined, Button.textOnly, Button.icon, Button.gradient, Button.rounded, Button.transparency.
Utilizzando NyFormActions
Usa il getter actions per inviare da qualsiasi punto:
LoginForm.actions.submit(
onSuccess: (data) {
print(data);
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
showToastError: true,
);
Utilizzando il metodo statico NyFormWidget.submit()
Invia un form per nome da qualsiasi punto:
NyFormWidget.submit("LoginForm",
onSuccess: (data) {
print(data);
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
showToastError: true,
);
Quando inviato, il form valida tutti i campi. Se valido, onSuccess viene chiamato con una Map<String, dynamic> dei dati dei campi (le chiavi sono versioni snake_case dei nomi dei campi). Se non valido, viene mostrato un toast di errore per impostazione predefinita e onFailure viene chiamato se fornito.
Tipi di Campo
Nylo Website v7 fornisce 22 tipi di campo tramite costruttori nominati sulla classe Field. Tutti i costruttori dei campi condividono questi parametri comuni:
| Parametro | Tipo | Predefinito | Descrizione |
|---|---|---|---|
key |
String |
Obbligatorio | L'identificatore del campo (posizionale) |
label |
String? |
null |
Etichetta personalizzata (predefinita alla chiave in title case) |
value |
dynamic |
null |
Valore iniziale |
validator |
FormValidator? |
null |
Regole di validazione |
autofocus |
bool |
false |
Auto-focus al caricamento |
dummyData |
String? |
null |
Dati di test/sviluppo |
header |
Widget? |
null |
Widget visualizzato sopra il campo |
footer |
Widget? |
null |
Widget visualizzato sotto il campo |
titleStyle |
TextStyle? |
null |
Stile personalizzato del testo dell'etichetta |
hidden |
bool |
false |
Nasconde il campo |
readOnly |
bool? |
null |
Rende il campo in sola lettura |
style |
FieldStyle? |
Varia | Configurazione di stile specifica del campo |
onChanged |
Function(dynamic)? |
null |
Callback di cambio valore |
Campi Testo
Field.text("Name")
Field.text("Name",
value: "John",
validator: FormValidator.notEmpty(),
autofocus: true,
)
Tipo di stile: FieldStyleTextField
Campi Numero
Field.number("Age")
// Decimal numbers
Field.number("Score", decimal: true)
Il parametro decimal controlla se l'input decimale e' consentito. Tipo di stile: FieldStyleTextField
Campi Password
Field.password("Password")
// With visibility toggle
Field.password("Password", viewable: true)
Il parametro viewable aggiunge un toggle mostra/nascondi. Tipo di stile: FieldStyleTextField
Campi Email
Field.email("Email", validator: FormValidator.email())
Imposta automaticamente il tipo di tastiera email e filtra gli spazi vuoti. Tipo di stile: FieldStyleTextField
Campi URL
Field.url("Website", validator: FormValidator.url())
Imposta il tipo di tastiera URL. Tipo di stile: FieldStyleTextField
Campi Area di Testo
Field.textArea("Description")
Input di testo multi-riga. Tipo di stile: FieldStyleTextField
Campi Numero di Telefono
Field.phoneNumber("Mobile Phone")
Formatta automaticamente l'input del numero di telefono. Tipo di stile: FieldStyleTextField
Iniziali Maiuscole per Parole
Field.capitalizeWords("Full Name")
Rende maiuscola la prima lettera di ogni parola. Tipo di stile: FieldStyleTextField
Iniziale Maiuscola per Frasi
Field.capitalizeSentences("Bio")
Rende maiuscola la prima lettera di ogni frase. Tipo di stile: FieldStyleTextField
Campi Data
Field.date("Birthday")
Field.date("Birthday",
dummyData: "1990-01-01",
style: FieldStyleDateTimePicker(
firstDate: DateTime(1900),
lastDate: DateTime.now(),
),
)
Apre un selettore di data. Tipo di stile: FieldStyleDateTimePicker
Campi Data e Ora
Field.datetime("Check in Date")
Field.datetime("Appointment", dummyData: "2025-01-01 10:00")
Apre un selettore di data e ora. Tipo di stile: FieldStyleDateTimePicker
Campi con Input Mascherato
Field.mask("Phone", mask: "(###) ###-####")
Field.mask("Credit Card", mask: "#### #### #### ####")
Field.mask("Custom Code",
mask: "AA-####",
match: r'[\w\d]',
maskReturnValue: true, // Returns the formatted value
)
Il carattere # nella maschera viene sostituito dall'input dell'utente. Usa match per controllare i caratteri consentiti. Quando maskReturnValue e' true, il valore restituito include la formattazione della maschera.
Campi Valuta
Field.currency("Price", currency: "usd")
Il parametro currency e' obbligatorio e determina il formato della valuta. Tipo di stile: FieldStyleTextField
Campi Checkbox
Field.checkbox("Accept Terms")
Field.checkbox("Agree to terms",
header: Text("Terms and conditions"),
footer: Text("You must agree to continue."),
validator: FormValidator.booleanTrue(message: "You must accept the terms"),
)
Tipo di stile: FieldStyleCheckbox
Campi Switch
Field.switchBox("Enable Notifications")
Tipo di stile: FieldStyleSwitchBox
Campi Picker
Field.picker("Category",
options: FormCollection.from(["Electronics", "Clothing", "Books"]),
)
// With key-value pairs
Field.picker("Country",
options: FormCollection.fromMap({
"us": "United States",
"ca": "Canada",
"uk": "United Kingdom",
}),
)
Il parametro options richiede una FormCollection (non una lista grezza). Vedi FormCollection per i dettagli. Tipo di stile: FieldStylePicker
Campi Radio
Field.radio("Newsletter",
options: FormCollection.fromMap({
"Yes": "Yes, I want to receive newsletters",
"No": "No, I do not want to receive newsletters",
}),
)
Il parametro options richiede una FormCollection. Tipo di stile: FieldStyleRadio
Campi Chip
Field.chips("Tags",
options: FormCollection.from(["Featured", "Sale", "New"]),
)
// With key-value pairs
Field.chips("Engine Size",
options: FormCollection.fromMap({
"125": "125cc",
"250": "250cc",
"500": "500cc",
}),
)
Permette la selezione multipla tramite widget chip. Il parametro options richiede una FormCollection. Tipo di stile: FieldStyleChip
Campi Slider
Field.slider("Rating",
label: "Rate us",
validator: FormValidator.minValue(4, message: "Rating must be at least 4"),
style: FieldStyleSlider(
min: 0,
max: 10,
divisions: 10,
activeColor: Colors.blue,
inactiveColor: Colors.grey,
),
)
Tipo di stile: FieldStyleSlider -- configura min, max, divisions, colori, visualizzazione del valore e altro.
Campi Range Slider
Field.rangeSlider("Price Range",
style: FieldStyleRangeSlider(
min: 0,
max: 1000,
divisions: 20,
activeColor: Colors.blue,
inactiveColor: Colors.grey,
),
)
Restituisce un oggetto RangeValues. Tipo di stile: FieldStyleRangeSlider
Campi Personalizzati
Usa Field.custom() per fornire il tuo widget stateful personalizzato:
Field.custom("My Field",
child: MyCustomFieldWidget(),
)
Il parametro child richiede un widget che estende NyFieldStatefulWidget. Questo ti da il pieno controllo sulla renderizzazione e il comportamento del campo.
Campi Widget
Usa Field.widget() per incorporare qualsiasi widget all'interno del form senza che sia un campo del form:
Field.widget(child: Divider())
Field.widget(child: Text("Section Header", style: TextStyle(fontSize: 18)))
I campi widget non partecipano alla validazione o alla raccolta dati. Sono puramente per il layout.
FormCollection
I campi picker, radio e chip richiedono una FormCollection per le loro opzioni. FormCollection fornisce un'interfaccia unificata per gestire diversi formati di opzioni.
Creare una FormCollection
// From a list of strings (value and label are the same)
FormCollection.from(["Red", "Green", "Blue"])
// Same as above, explicit
FormCollection.fromArray(["Red", "Green", "Blue"])
// From a map (key = value, value = label)
FormCollection.fromMap({
"us": "United States",
"ca": "Canada",
})
// From structured data (useful for API responses)
FormCollection.fromKeyValue([
{"value": "en", "label": "English"},
{"value": "es", "label": "Spanish"},
])
FormCollection.from() rileva automaticamente il formato dei dati e delega al costruttore appropriato.
FormOption
Ogni opzione in una FormCollection e' un FormOption con proprieta' value e label:
FormOption option = FormOption(value: "us", label: "United States");
print(option.value); // "us"
print(option.label); // "United States"
Interrogare le Opzioni
FormCollection options = FormCollection.fromMap({"us": "United States", "ca": "Canada"});
options.getByValue("us"); // FormOption(value: us, label: United States)
options.getLabelByValue("us"); // "United States"
options.containsValue("ca"); // true
options.searchByLabel("can"); // [FormOption(value: ca, label: Canada)]
options.values; // ["us", "ca"]
options.labels; // ["United States", "Canada"]
Validazione del Form
Aggiungi la validazione a qualsiasi campo utilizzando il parametro validator con FormValidator:
// Named constructor
Field.email("Email", validator: FormValidator.email())
// Chained rules
Field.text("Username",
validator: FormValidator()
.notEmpty()
.minLength(3)
.maxLength(20)
)
// Password with strength level
Field.password("Password",
validator: FormValidator.password(strength: 2)
)
// Boolean validation
Field.checkbox("Terms",
validator: FormValidator.booleanTrue(message: "You must accept the terms")
)
// Custom inline validation
Field.number("Age",
validator: FormValidator.custom(
message: "Age must be between 18 and 100",
validate: (data) {
int? age = int.tryParse(data.toString());
return age != null && age >= 18 && age <= 100;
},
)
)
Quando un form viene inviato, tutti i validatori vengono controllati. Se qualcuno fallisce, viene mostrato un toast di errore con il primo messaggio di errore e viene chiamato il callback onFailure.
Vedi anche: Per un elenco completo dei validatori disponibili, consulta la pagina Validazione.
Gestione dei Dati del Form
Dati Iniziali
Ci sono due modi per impostare i dati iniziali su un form.
Opzione 1: Sovrascrivere il getter init nella classe del form
class EditAccountForm extends NyFormWidget {
EditAccountForm({super.key, super.submitButton, super.onSubmit, super.onFailure});
@override
Function()? get init => () async {
final user = await api<ApiService>((request) => request.getUserData());
return {
"First Name": user?.firstName,
"Last Name": user?.lastName,
};
};
@override
fields() => [
Field.text("First Name"),
Field.text("Last Name"),
];
static NyFormActions get actions => const NyFormActions('EditAccountForm');
}
Il getter init puo' restituire sia una Map sincrona che una Future<Map> asincrona. Le chiavi vengono abbinate ai nomi dei campi utilizzando la normalizzazione snake_case, quindi "First Name" si mappa a un campo con chiave "First Name".
Opzione 2: Passare initialData al widget del form
EditAccountForm(
initialData: {
"first_name": "John",
"last_name": "Doe",
},
)
Impostare i Valori dei Campi
Usa NyFormActions per impostare i valori dei campi da qualsiasi punto:
// Set a single field value
EditAccountForm.actions.updateField("First Name", "Jane");
Impostare le Opzioni dei Campi
Aggiorna le opzioni sui campi picker, chip o radio dinamicamente:
EditAccountForm.actions.setOptions("Category", FormCollection.from(["New Option 1", "New Option 2"]));
Leggere i Dati del Form
I dati del form vengono acceduti tramite il callback onSubmit quando il form viene inviato, o tramite il callback onChanged per aggiornamenti in tempo reale:
EditAccountForm(
onSubmit: (data) {
// data is a Map<String, dynamic>
// {first_name: "Jane", last_name: "Doe", email: "jane@example.com"}
print(data);
},
onChanged: (Field field, dynamic value) {
print("${field.key} changed to: $value");
},
)
Cancellare i Dati
// Clear all fields
EditAccountForm.actions.clear();
// Clear a specific field
EditAccountForm.actions.clearField("First Name");
Aggiornare i Campi
// Update a field value
EditAccountForm.actions.updateField("First Name", "Jane");
// Refresh the form UI
EditAccountForm.actions.refresh();
// Refresh form fields (re-calls fields())
EditAccountForm.actions.refreshForm();
Pulsante di Invio
Passa un submitButton e un callback onSubmit quando costruisci il form:
UserInfoForm(
submitButton: Button.primary(text: "Submit"),
onSubmit: (data) {
print(data);
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
)
Il submitButton viene automaticamente visualizzato sotto i campi del form. Puoi usare qualsiasi stile di pulsante integrato o un widget personalizzato.
Puoi anche usare qualsiasi widget come pulsante di invio passandolo come footer:
UserInfoForm(
onSubmit: (data) {
print(data);
},
footer: ElevatedButton(
onPressed: () {
UserInfoForm.actions.submit(
onSuccess: (data) {
print(data);
},
);
},
child: Text("Submit"),
),
)
Layout del Form
Posiziona i campi affiancati racchiudendoli in una List:
@override
fields() => [
// Single field (full width)
Field.text("Title"),
// Two fields in a row
[
Field.text("First Name"),
Field.text("Last Name"),
],
// Another single field
Field.textArea("Bio"),
// Slider and range slider in a row
[
Field.slider("Rating", style: FieldStyleSlider(min: 0, max: 10)),
Field.rangeSlider("Budget", style: FieldStyleRangeSlider(min: 0, max: 1000)),
],
// Embed a non-field widget
Field.widget(child: Divider()),
Field.email("Email"),
];
I campi in una List vengono renderizzati in una Row con larghezze Expanded uguali. La spaziatura tra i campi e' controllata dal parametro crossAxisSpacing su NyFormWidget.
Visibilita' dei Campi
Mostra o nascondi i campi programmaticamente utilizzando i metodi hide() e show() su Field. Puoi accedere ai campi all'interno della tua classe form o tramite il callback onChanged:
// Inside your NyFormWidget subclass or onChanged callback
Field nameField = ...;
// Hide the field
nameField.hide();
// Show the field
nameField.show();
I campi nascosti non vengono renderizzati nell'UI ma esistono ancora nella lista dei campi del form.
Stile dei Campi
Ogni tipo di campo ha una sottoclasse FieldStyle corrispondente per lo stile:
| Tipo di Campo | Classe di Stile |
|---|---|
| Text, Email, Password, Number, URL, TextArea, PhoneNumber, Currency, Mask, CapitalizeWords, CapitalizeSentences | FieldStyleTextField |
| Date, DateTime | FieldStyleDateTimePicker |
| Picker | FieldStylePicker |
| Checkbox | FieldStyleCheckbox |
| Switch Box | FieldStyleSwitchBox |
| Radio | FieldStyleRadio |
| Chip | FieldStyleChip |
| Slider | FieldStyleSlider |
| Range Slider | FieldStyleRangeSlider |
Passa un oggetto di stile al parametro style di qualsiasi campo:
Field.text("Name",
style: FieldStyleTextField(
filled: true,
fillColor: Colors.grey.shade100,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
prefixIcon: Icon(Icons.person),
),
)
Field.slider("Rating",
style: FieldStyleSlider(
min: 0,
max: 10,
divisions: 10,
activeColor: Colors.blue,
showValue: true,
),
)
Field.chips("Tags",
options: FormCollection.from(["Sale", "New", "Featured"]),
style: FieldStyleChip(
selectedColor: Colors.blue,
checkmarkColor: Colors.white,
spacing: 8.0,
runSpacing: 8.0,
),
)
Metodi Statici di NyFormWidget
NyFormWidget fornisce metodi statici per interagire con i form per nome da qualsiasi punto della tua app:
| Metodo | Descrizione |
|---|---|
NyFormWidget.submit(name, onSuccess:, onFailure:, showToastError:) |
Invia un form per nome |
NyFormWidget.stateRefresh(name) |
Aggiorna lo stato dell'UI del form |
NyFormWidget.stateSetValue(name, key, value) |
Imposta il valore di un campo per nome del form |
NyFormWidget.stateSetOptions(name, key, options) |
Imposta le opzioni di un campo per nome del form |
NyFormWidget.stateClearData(name) |
Cancella tutti i campi per nome del form |
NyFormWidget.stateRefreshForm(name) |
Aggiorna i campi del form (richiama fields()) |
// Submit a form named "LoginForm" from anywhere
NyFormWidget.submit("LoginForm", onSuccess: (data) {
print(data);
});
// Update a field value remotely
NyFormWidget.stateSetValue("LoginForm", "Email", "new@email.com");
// Clear all form data
NyFormWidget.stateClearData("LoginForm");
Suggerimento: Preferisci usare
NyFormActions(vedi sotto) invece di chiamare direttamente questi metodi statici -- e' piu' conciso e meno soggetto a errori.
Riferimento Costruttore di NyFormWidget
Quando estendi NyFormWidget, questi sono i parametri del costruttore che puoi passare:
LoginForm(
Key? key,
double crossAxisSpacing = 10, // Horizontal spacing between row fields
double mainAxisSpacing = 10, // Vertical spacing between fields
Map<String, dynamic>? initialData, // Initial field values
Function(Field field, dynamic value)? onChanged, // Field change callback
Widget? header, // Widget above the form
Widget? submitButton, // Submit button widget
Widget? footer, // Widget below the form
double headerSpacing = 10, // Spacing after header
double submitButtonSpacing = 10, // Spacing after submit button
double footerSpacing = 10, // Spacing before footer
LoadingStyle? loadingStyle, // Loading indicator style
bool locked = false, // Makes form read-only
Function(dynamic data)? onSubmit, // Called with form data on successful validation
Function(dynamic error)? onFailure, // Called with errors on failed validation
)
Il callback onChanged riceve il Field che e' cambiato e il suo nuovo valore:
LoginForm(
onChanged: (Field field, dynamic value) {
print("${field.key} changed to: $value");
},
)
NyFormActions
NyFormActions fornisce un modo conveniente per interagire con un form da qualsiasi punto della tua app. Definiscilo come getter statico nella tua classe form:
class LoginForm extends NyFormWidget {
LoginForm({super.key, super.submitButton, super.onSubmit, super.onFailure});
@override
fields() => [
Field.email("Email", validator: FormValidator.email()),
Field.password("Password", validator: FormValidator.password()),
];
static NyFormActions get actions => const NyFormActions('LoginForm');
}
Azioni Disponibili
| Metodo | Descrizione |
|---|---|
actions.updateField(key, value) |
Imposta il valore di un campo |
actions.clearField(key) |
Cancella un campo specifico |
actions.clear() |
Cancella tutti i campi |
actions.refresh() |
Aggiorna lo stato dell'UI del form |
actions.refreshForm() |
Richiama fields() e ricostruisci |
actions.setOptions(key, options) |
Imposta le opzioni sui campi picker/chip/radio |
actions.submit(onSuccess:, onFailure:, showToastError:) |
Invia con validazione |
// Update a field value
LoginForm.actions.updateField("Email", "new@email.com");
// Clear all form data
LoginForm.actions.clear();
// Submit the form
LoginForm.actions.submit(
onSuccess: (data) {
print(data);
},
);
Override di NyFormWidget
Metodi che puoi sovrascrivere nella tua sottoclasse di NyFormWidget:
| Override | Descrizione |
|---|---|
fields() |
Definisci i campi del form (obbligatorio) |
init |
Fornisci dati iniziali (sincroni o asincroni) |
onChange(field, data) |
Gestisci i cambiamenti dei campi internamente |
Riferimento Tutti i Tipi di Campo
| Costruttore | Parametri Chiave | Descrizione |
|---|---|---|
Field.text() |
-- | Input di testo standard |
Field.email() |
-- | Input email con tipo di tastiera |
Field.password() |
viewable |
Password con toggle di visibilita' opzionale |
Field.number() |
decimal |
Input numerico, decimale opzionale |
Field.currency() |
currency (obbligatorio) |
Input formattato come valuta |
Field.capitalizeWords() |
-- | Input testo in title case |
Field.capitalizeSentences() |
-- | Input testo con iniziale maiuscola per frase |
Field.textArea() |
-- | Input testo multi-riga |
Field.phoneNumber() |
-- | Numero di telefono auto-formattato |
Field.url() |
-- | Input URL con tipo di tastiera |
Field.mask() |
mask (obbligatorio), match, maskReturnValue |
Input testo mascherato |
Field.date() |
-- | Selettore data |
Field.datetime() |
-- | Selettore data e ora |
Field.checkbox() |
-- | Checkbox booleano |
Field.switchBox() |
-- | Toggle switch booleano |
Field.picker() |
options (obbligatorio FormCollection) |
Selezione singola da lista |
Field.radio() |
options (obbligatorio FormCollection) |
Gruppo di pulsanti radio |
Field.chips() |
options (obbligatorio FormCollection) |
Chip a selezione multipla |
Field.slider() |
-- | Slider a valore singolo |
Field.rangeSlider() |
-- | Slider a intervallo di valori |
Field.custom() |
child (obbligatorio NyFieldStatefulWidget) |
Widget stateful personalizzato |
Field.widget() |
child (obbligatorio Widget) |
Incorpora qualsiasi widget (non-campo) |