Basics

Formulaires

Introduction

Nylo Website v7 fournit un systeme de formulaires construit autour de NyFormWidget. Votre classe de formulaire etend NyFormWidget et est le widget -- aucun wrapper separe n'est necessaire. Les formulaires prennent en charge la validation integree, de nombreux types de champs, le style et la gestion des donnees.

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: "..."}
  },
)

Creer un formulaire

Utilisez le Metro CLI pour creer un nouveau formulaire :

metro make:form LoginForm

Cela cree 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');
}

Les formulaires etendent NyFormWidget et surchargent la methode fields() pour definir les champs du formulaire. Chaque champ utilise un constructeur nomme comme Field.text(), Field.email() ou Field.password(). Le getter static NyFormActions get actions offre un moyen pratique d'interagir avec le formulaire depuis n'importe ou dans votre application.

Afficher un formulaire

Puisque votre classe de formulaire etend NyFormWidget, elle est le widget. Utilisez-le directement dans votre arbre de widgets :

@override
Widget view(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: LoginForm(
        submitButton: Button.primary(text: "Submit"),
        onSubmit: (data) {
          print(data);
        },
      ),
    ),
  );
}

Soumettre un formulaire

Il existe trois facons de soumettre un formulaire :

Utiliser onSubmit et submitButton

Passez onSubmit et un submitButton lors de la construction du formulaire. Nylo Website fournit des boutons preconstruits qui fonctionnent comme boutons de soumission :

LoginForm(
  submitButton: Button.primary(text: "Submit"),
  onSubmit: (data) {
    print(data); // {email: "...", password: "..."}
  },
  onFailure: (errors) {
    print(errors.first.rule.getMessage());
  },
)

Styles de boutons disponibles : Button.primary, Button.secondary, Button.outlined, Button.textOnly, Button.icon, Button.gradient, Button.rounded, Button.transparency.

Utiliser NyFormActions

Utilisez le getter actions pour soumettre depuis n'importe ou :

LoginForm.actions.submit(
  onSuccess: (data) {
    print(data);
  },
  onFailure: (errors) {
    print(errors.first.rule.getMessage());
  },
  showToastError: true,
);

Utiliser la methode statique NyFormWidget.submit()

Soumettez un formulaire par son nom depuis n'importe ou :

NyFormWidget.submit("LoginForm",
  onSuccess: (data) {
    print(data);
  },
  onFailure: (errors) {
    print(errors.first.rule.getMessage());
  },
  showToastError: true,
);

Lors de la soumission, le formulaire valide tous les champs. Si la validation reussit, onSuccess est appele avec un Map<String, dynamic> des donnees du formulaire (les cles sont des versions snake_case des noms de champs). Si la validation echoue, une erreur toast est affichee par defaut et onFailure est appele si fourni.

Types de champs

Nylo Website v7 fournit 22 types de champs via des constructeurs nommes sur la classe Field. Tous les constructeurs de champs partagent ces parametres communs :

Parametre Type Defaut Description
key String Requis L'identifiant du champ (positionnel)
label String? null Libelle d'affichage personnalise (par defaut la cle en titre)
value dynamic null Valeur initiale
validator FormValidator? null Regles de validation
autofocus bool false Focus automatique au chargement
dummyData String? null Donnees de test/developpement
header Widget? null Widget affiche au-dessus du champ
footer Widget? null Widget affiche en dessous du champ
titleStyle TextStyle? null Style de texte personnalise pour le libelle
hidden bool false Masquer le champ
readOnly bool? null Rendre le champ en lecture seule
style FieldStyle? Variable Configuration de style specifique au champ
onChanged Function(dynamic)? null Callback de changement de valeur

Champs texte

Field.text("Name")

Field.text("Name",
  value: "John",
  validator: FormValidator.notEmpty(),
  autofocus: true,
)

Type de style : FieldStyleTextField

Champs numeriques

Field.number("Age")

// Decimal numbers
Field.number("Score", decimal: true)

Le parametre decimal controle si la saisie decimale est autorisee. Type de style : FieldStyleTextField

Champs mot de passe

Field.password("Password")

// With visibility toggle
Field.password("Password", viewable: true)

Le parametre viewable ajoute un bouton afficher/masquer. Type de style : FieldStyleTextField

Champs email

Field.email("Email", validator: FormValidator.email())

Configure automatiquement le type de clavier email et filtre les espaces. Type de style : FieldStyleTextField

Champs URL

Field.url("Website", validator: FormValidator.url())

Configure le type de clavier URL. Type de style : FieldStyleTextField

Champs zone de texte

Field.textArea("Description")

Saisie de texte multiligne. Type de style : FieldStyleTextField

Champs numero de telephone

Field.phoneNumber("Mobile Phone")

Formate automatiquement la saisie du numero de telephone. Type de style : FieldStyleTextField

Majuscule aux mots

Field.capitalizeWords("Full Name")

Met en majuscule la premiere lettre de chaque mot. Type de style : FieldStyleTextField

Majuscule aux phrases

Field.capitalizeSentences("Bio")

Met en majuscule la premiere lettre de chaque phrase. Type de style : FieldStyleTextField

Champs date

Field.date("Birthday")

Field.date("Birthday",
  dummyData: "1990-01-01",
  style: FieldStyleDateTimePicker(
    firstDate: DateTime(1900),
    lastDate: DateTime.now(),
  ),
)

Ouvre un selecteur de date. Type de style : FieldStyleDateTimePicker

Champs date et heure

Field.datetime("Check in Date")

Field.datetime("Appointment", dummyData: "2025-01-01 10:00")

Ouvre un selecteur de date et d'heure. Type de style : FieldStyleDateTimePicker

Champs saisie masquee

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
)

Le caractere # dans le masque est remplace par la saisie de l'utilisateur. Utilisez match pour controler les caracteres autorises. Lorsque maskReturnValue est true, la valeur retournee inclut le formatage du masque.

Champs devise

Field.currency("Price", currency: "usd")

Le parametre currency est requis et determine le format de la devise. Type de style : FieldStyleTextField

Champs case a cocher

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"),
)

Type de style : FieldStyleCheckbox

Champs interrupteur

Field.switchBox("Enable Notifications")

Type de style : FieldStyleSwitchBox

Champs selecteur

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",
  }),
)

Le parametre options necessite un FormCollection (pas une liste brute). Voir FormCollection pour plus de details. Type de style : FieldStylePicker

Champs bouton radio

Field.radio("Newsletter",
  options: FormCollection.fromMap({
    "Yes": "Yes, I want to receive newsletters",
    "No": "No, I do not want to receive newsletters",
  }),
)

Le parametre options necessite un FormCollection. Type de style : FieldStyleRadio

Champs 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",
  }),
)

Permet la selection multiple via des widgets chip. Le parametre options necessite un FormCollection. Type de style : FieldStyleChip

Champs curseur

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,
  ),
)

Type de style : FieldStyleSlider -- configurez min, max, divisions, les couleurs, l'affichage de la valeur, et plus encore.

Champs curseur de plage

Field.rangeSlider("Price Range",
  style: FieldStyleRangeSlider(
    min: 0,
    max: 1000,
    divisions: 20,
    activeColor: Colors.blue,
    inactiveColor: Colors.grey,
  ),
)

Retourne un objet RangeValues. Type de style : FieldStyleRangeSlider

Champs personnalises

Utilisez Field.custom() pour fournir votre propre widget avec etat :

Field.custom("My Field",
  child: MyCustomFieldWidget(),
)

Le parametre child necessite un widget qui etend NyFieldStatefulWidget. Cela vous donne un controle total sur le rendu et le comportement du champ.

Champs widget

Utilisez Field.widget() pour integrer n'importe quel widget dans le formulaire sans qu'il soit un champ de formulaire :

Field.widget(child: Divider())

Field.widget(child: Text("Section Header", style: TextStyle(fontSize: 18)))

Les champs widget ne participent pas a la validation ni a la collecte de donnees. Ils sont purement destines a la mise en page.

FormCollection

Les champs selecteur, radio et chip necessitent un FormCollection pour leurs options. FormCollection fournit une interface unifiee pour gerer differents formats d'options.

Creer un 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() detecte automatiquement le format des donnees et delegue au constructeur approprie.

FormOption

Chaque option dans un FormCollection est un FormOption avec les proprietes value et label :

FormOption option = FormOption(value: "us", label: "United States");
print(option.value); // "us"
print(option.label); // "United States"

Interroger les options

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"]

Validation de formulaire

Ajoutez la validation a n'importe quel champ en utilisant le parametre validator avec 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;
    },
  )
)

Lors de la soumission d'un formulaire, tous les validateurs sont verifies. Si l'un echoue, une erreur toast affiche le premier message d'erreur et le callback onFailure est appele.

Voir aussi : Pour une liste complete des validateurs disponibles, consultez la page Validation.

Gestion des donnees du formulaire

Donnees initiales

Il existe deux facons de definir les donnees initiales d'un formulaire.

Option 1 : Surcharger le getter init dans votre classe de formulaire

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');
}

Le getter init peut retourner soit un Map synchrone, soit un Future<Map> asynchrone. Les cles sont associees aux noms de champs en utilisant la normalisation snake_case, donc "First Name" correspond a un champ avec la cle "First Name".

Option 2 : Passer initialData au widget du formulaire

EditAccountForm(
  initialData: {
    "first_name": "John",
    "last_name": "Doe",
  },
)

Definir les valeurs des champs

Utilisez NyFormActions pour definir les valeurs des champs depuis n'importe ou :

// Set a single field value
EditAccountForm.actions.updateField("First Name", "Jane");

Definir les options des champs

Mettez a jour les options des champs selecteur, chip ou radio de maniere dynamique :

EditAccountForm.actions.setOptions("Category", FormCollection.from(["New Option 1", "New Option 2"]));

Lire les donnees du formulaire

Les donnees du formulaire sont accessibles via le callback onSubmit lors de la soumission du formulaire, ou via le callback onChanged pour les mises a jour en temps reel :

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");
  },
)

Effacer les donnees

// Clear all fields
EditAccountForm.actions.clear();

// Clear a specific field
EditAccountForm.actions.clearField("First Name");

Mettre a jour les champs

// 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();

Bouton de soumission

Passez un submitButton et un callback onSubmit lors de la construction du formulaire :

UserInfoForm(
  submitButton: Button.primary(text: "Submit"),
  onSubmit: (data) {
    print(data);
  },
  onFailure: (errors) {
    print(errors.first.rule.getMessage());
  },
)

Le submitButton est automatiquement affiche sous les champs du formulaire. Vous pouvez utiliser n'importe lequel des styles de boutons integres ou un widget personnalise.

Vous pouvez egalement utiliser n'importe quel widget comme bouton de soumission en le passant comme footer :

UserInfoForm(
  onSubmit: (data) {
    print(data);
  },
  footer: ElevatedButton(
    onPressed: () {
      UserInfoForm.actions.submit(
        onSuccess: (data) {
          print(data);
        },
      );
    },
    child: Text("Submit"),
  ),
)

Disposition du formulaire

Placez les champs cote a cote en les enveloppant dans une 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"),
];

Les champs dans une List sont affiches dans un Row avec des largeurs Expanded egales. L'espacement entre les champs est controle par le parametre crossAxisSpacing sur NyFormWidget.

Visibilite des champs

Affichez ou masquez les champs par programmation en utilisant les methodes hide() et show() sur Field. Vous pouvez acceder aux champs a l'interieur de votre classe de formulaire ou via le callback onChanged :

// Inside your NyFormWidget subclass or onChanged callback
Field nameField = ...;

// Hide the field
nameField.hide();

// Show the field
nameField.show();

Les champs masques ne sont pas affiches dans l'interface utilisateur mais existent toujours dans la liste des champs du formulaire.

Style des champs

Chaque type de champ possede une sous-classe FieldStyle correspondante pour le style :

Type de champ Classe de style
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

Passez un objet de style au parametre style de n'importe quel champ :

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,
  ),
)

Methodes statiques NyFormWidget

NyFormWidget fournit des methodes statiques pour interagir avec les formulaires par nom depuis n'importe ou dans votre application :

Methode Description
NyFormWidget.submit(name, onSuccess:, onFailure:, showToastError:) Soumettre un formulaire par son nom
NyFormWidget.stateRefresh(name) Rafraichir l'etat de l'interface du formulaire
NyFormWidget.stateSetValue(name, key, value) Definir la valeur d'un champ par nom de formulaire
NyFormWidget.stateSetOptions(name, key, options) Definir les options d'un champ par nom de formulaire
NyFormWidget.stateClearData(name) Effacer tous les champs par nom de formulaire
NyFormWidget.stateRefreshForm(name) Rafraichir les champs du formulaire (re-appelle 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");

Conseil : Preferez utiliser NyFormActions (voir ci-dessous) au lieu d'appeler ces methodes statiques directement -- c'est plus concis et moins sujet aux erreurs.

Reference du constructeur NyFormWidget

Lors de l'extension de NyFormWidget, voici les parametres du constructeur que vous pouvez passer :

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
)

Le callback onChanged recoit le Field qui a change et sa nouvelle valeur :

LoginForm(
  onChanged: (Field field, dynamic value) {
    print("${field.key} changed to: $value");
  },
)

NyFormActions

NyFormActions fournit un moyen pratique d'interagir avec un formulaire depuis n'importe ou dans votre application. Definissez-le comme un getter statique sur votre classe de formulaire :

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');
}

Actions disponibles

Methode Description
actions.updateField(key, value) Definir la valeur d'un champ
actions.clearField(key) Effacer un champ specifique
actions.clear() Effacer tous les champs
actions.refresh() Rafraichir l'etat de l'interface du formulaire
actions.refreshForm() Re-appeler fields() et reconstruire
actions.setOptions(key, options) Definir les options sur les champs selecteur/chip/radio
actions.submit(onSuccess:, onFailure:, showToastError:) Soumettre avec validation
// 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);
  },
);

Surcharges NyFormWidget

Methodes que vous pouvez surcharger dans votre sous-classe NyFormWidget :

Surcharge Description
fields() Definir les champs du formulaire (requis)
init Fournir les donnees initiales (synchrone ou asynchrone)
onChange(field, data) Gerer les changements de champs en interne

Reference de tous les types de champs

Constructeur Parametres cles Description
Field.text() -- Saisie de texte standard
Field.email() -- Saisie email avec type de clavier
Field.password() viewable Mot de passe avec bouton de visibilite optionnel
Field.number() decimal Saisie numerique, decimale optionnelle
Field.currency() currency (requis) Saisie formatee en devise
Field.capitalizeWords() -- Saisie de texte en casse titre
Field.capitalizeSentences() -- Saisie de texte en casse phrase
Field.textArea() -- Saisie de texte multiligne
Field.phoneNumber() -- Numero de telephone formate automatiquement
Field.url() -- Saisie URL avec type de clavier
Field.mask() mask (requis), match, maskReturnValue Saisie de texte masquee
Field.date() -- Selecteur de date
Field.datetime() -- Selecteur de date et heure
Field.checkbox() -- Case a cocher booleenne
Field.switchBox() -- Interrupteur a bascule booleen
Field.picker() options (FormCollection requis) Selection unique dans une liste
Field.radio() options (FormCollection requis) Groupe de boutons radio
Field.chips() options (FormCollection requis) Chips a selection multiple
Field.slider() -- Curseur a valeur unique
Field.rangeSlider() -- Curseur a plage de valeurs
Field.custom() child (NyFieldStatefulWidget requis) Widget avec etat personnalise
Field.widget() child (Widget requis) Integrer n'importe quel widget (non-champ)