Basics

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)