Basics

Forms



Introduction

Nylo's form system provides:

  • Easy form creation and management
  • Built-in validation
  • Field type casting
  • Form state management
  • Styling customization
  • Data handling utilities


Creating a form

First, run the below metro command from your terminal.

dart run nylo_framework:main make:form LoginForm
# or with Metro alias
metro make:form LoginForm

This will create a new form class lib/app/forms/login_form.dart

E.g. The newly created LoginForm

import 'package:nylo_framework/nylo_framework.dart';

class LoginForm extends NyFormData {

  LoginForm({String? name}) : super(name ?? "login");

  // Add your fields here
  @override
  fields() => [
    Field.email("Email", 
        validator: FormValidator.email()
    ),
    Field.password("Password",
        validator: FormValidator.password()
    ),
  ];
}

Displaying a form

To display a form, you can use the NyForm widget.

import 'package:nylo_framework/nylo_framework.dart';
import '/app/forms/login_form.dart';
import '/resources/widgets/buttons/buttons.dart';
...

// Create your form
LoginForm form = LoginForm();

// add the form using the NyForm widget
@override
Widget view(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: NyForm(
        form: form, 
        footer: Button.primary(child: "Submit", submitForm: (form, (data) {
              printInfo(data);
          }),
        ),
      ),
    )
  );
}

This is all you need to create and display a form in Nylo.

The UI of this page will now contain two fields, Email and Password, and a submit button.

Using the Button widget to submit the form

Out the box, Nylo provides 8 pre-built buttons that you can use to submit a form.

Each button has a different style and color.

  • Button.primary
  • Button.secondary
  • Button.outlined
  • Button.textOnly
  • Button.icon
  • Button.gradient
  • Button.rounded
  • Button.transparency

They all have the ability to submit a form using the submitForm parameter.

Button.primary(text: "Submit", submitForm: (form, (data) {
    printInfo(data);
}));

Submitting the form via a different widget

To submit a form, you can call the submit method on a form.

LoginForm form = LoginForm();

@override
Widget view(BuildContext context) {
    return Scaffold(
        body: SafeArea(
          child: NyForm(
            form: form, 
            footer: MaterialButton(
                onPressed: () {
                    form.submit(onSuccess: (data) {
                        // Do something with the data
                    });
                },
                child: Text("Submit"),
            )),
        )
    );
}

When you call form.submit(), Nylo will validate the form and if the form is valid, it will call the onSuccess callback with the form data.

That's a quick overview of how to create, display and submit a form in Nylo.

This is just scratching the surface, you can customize your forms even further by adding casts, validation rules, dummy data and global styles.


Creating Forms

Using the Metro CLI

The easiest way to create a new form is using the Metro CLI:

metro make:form LoginForm

# or
dart run nylo_framework:main make:form LoginForm

This creates a new form class in lib/app/forms/login_form.dart.

Form Structure

Forms in Nylo extend the NyFormData class:

class ProductForm extends NyFormData {
  ProductForm({String? name}) : super(name ?? "product");

  @override
  fields() => [
    // Define form fields here
    Field.text("Name"),
    Field.number("Price"),
    Field.textarea("Description")
  ];
}


Field Types

Nylo provides multiple ways to define fields, with the recommended approach using static methods for cleaner syntax:


Text Fields

// Recommended approach
Field.text("Name"),
Field.textarea("Description"),
Field.email("Email"),
Field.capitalizeWords("Title"),
Field.url("Website"),

// Alternative approach using constructor with casts
Field("Name", cast: FormCast.text()),
Field("Description", cast: FormCast.textArea())


Numeric Fields

// Recommended approach
Field.number("Age"),
Field.currency("Price", currency: "usd"),
Field.decimal("Score"),

// Alternative approach
Field("Age", cast: FormCast.number()),
Field("Price", cast: FormCast.currency("usd"))


Selection Fields

// Recommended approach
Field.picker("Category", options: ["Electronics", "Clothing", "Books"]),
Field.chips("Tags", options: ["Featured", "Sale", "New"]),

// Alternative approach
Field("Category", 
  cast: FormCast.picker(
    options: ["Electronics", "Clothing", "Books"]
  )
)


Boolean Fields

// Recommended approach
Field.checkbox("Accept Terms"),
Field.switchBox("Enable Notifications"),

// Alternative approach
Field("Accept Terms", cast: FormCast.checkbox()),
Field("Enable Notifications", cast: FormCast.switchBox())


Date and Time Fields

// Recommended approach
Field.date("Birth Date", 
  firstDate: DateTime(1900),
  lastDate: DateTime.now()
),
Field.datetime("Appointment"),

// Alternative approach
Field("Birth Date", 
  cast: FormCast.date(
    firstDate: DateTime(1900),
    lastDate: DateTime.now()
  )
)


Password Fields

// Recommended approach
Field.password("Password", viewable: true)

// Alternative approach
Field("Password", cast: FormCast.password(viewable: true))


Masked Input Fields

// Recommended approach
Field.mask("Phone", mask: "(###) ###-####"),
Field.mask("Credit Card", mask: "#### #### #### ####")

// Alternative approach
Field("Phone", 
  cast: FormCast.mask(mask: "(###) ###-####")
)


Form Validation

Nylo provides extensive validation capabilities:

Basic Validation

Field.text("Username",
  validate: FormValidator()
    .notEmpty()
    .minLength(3)
    .maxLength(20)
)

Combined Validation

Field.password("Password",
  validate: FormValidator()
    .notEmpty()
    .minLength(8)
    .password(strength: 2)
)

Custom Validation

Field.number("Age",
  validate: FormValidator.custom(
    (value) {
      if (value < 18) return false;
      if (value > 100) return false;
      return true;
    },
    message: "Age must be between 18 and 100"
  )
)

Validation Examples

// Email validation
Field.email("Email", 
  validate: FormValidator.email(
    message: "Please enter a valid email address"
  )
)

// Password validation with strength levels
Field.password("Password",
  validate: FormValidator.password(strength: 2)
)

// Length validation
Field.text("Username", 
  validate: FormValidator()
    .minLength(3)
    .maxLength(20)
)

// Phone number validation
Field.phone("Phone",
  validate: FormValidator.phoneNumberUs()  // US format
  // or
  validate: FormValidator.phoneNumberUk()  // UK format
)

// URL validation
Field.url("Website",
  validate: FormValidator.url()
)

// Contains validation
Field.picker("Category",
  validate: FormValidator.contains(["Tech", "Health", "Sports"])
)

// Numeric validation
Field.number("Age",
  validate: FormValidator()
    .numeric()
    .minValue(18)
    .maxValue(100)
)

// Date validation
Field.date("EventDate",
  validate: FormValidator()
    .date()
    .dateInFuture()
)

// Boolean validation
Field.checkbox("Terms",
  validate: FormValidator.isTrue()
)

// Multiple validators
Field.text("Username",
  validate: FormValidator()
    .notEmpty()
    .minLength(3)
    .maxLength(20)
    .regex(r'^[a-zA-Z0-9_]+$')
)

Available Validators

Validator Description
notEmpty() Ensures field is not empty
email() Validates email format
minLength(n) Minimum length check
maxLength(n) Maximum length check
numeric() Numbers only
regex(pattern) Custom regex pattern
contains(list) Must contain value from list
dateInPast() Date must be in past
dateInFuture() Date must be in future
password(strength: 1|2) Password strength validation
phoneNumberUs() US phone format
phoneNumberUk() UK phone format
url() Valid URL format
zipcodeUs() US zipcode format
postcodeUk() UK postcode format
isTrue() Must be true
isFalse() Must be false
dateAgeIsYounger(age) Age younger than specified
dateAgeIsOlder(age) Age older than specified


Form Casts

Casts transform field input into specific formats:

Available Casts

Cast Description Example
FormCast.email() Email input user@example.com
FormCast.number() Numeric input 42
FormCast.currency("usd") Currency formatting $42.99
FormCast.capitalizeWords() Title case Hello World
FormCast.date() Date picker 2024-01-15
FormCast.mask() Custom input mask (123) 456-7890
FormCast.picker() Selection list -
FormCast.chips() Multi-select chips -
FormCast.checkbox() Boolean checkbox -
FormCast.switchBox() Boolean switch -
FormCast.textarea() Multi-line text -
FormCast.password() Password input -

Custom Casts

Create custom casts in config/form_casts.dart:

final Map<String, dynamic> formCasts = {
  "phone": (Field field, Function(dynamic value)? onChanged) {
    return CustomPhoneField(
      field: field,
      onChanged: onChanged
    );
  }
};


Managing Form Data

Setting Initial Data

NyForm(
  form: form,
  loadData: () async {
    // final userData = api<ApiService>
    ;
    return {
      "name": "John Doe",
      "email": "john@example.com"
    };
  }
)

// or 
NyForm(
  form: form,
  initialData: {
    "name": "John Doe",
    "email": "john@example.com"
  }
)

Updating Data

// Update single field
form.setField("name", "Jane Doe");

// Update multiple fields
form.setData({
  "name": "Jane Doe",
  "email": "jane@example.com"
});

Clearing Data

// Clear everything
form.clear();

// Clear specific field
form.clearField("name");


Form Styling

Global Styles

Define global styles in app/forms/style/form_style.dart:

class FormStyle extends NyFormStyle {
  @override
  FormStyleTextField textField(BuildContext context, Field field) {
    return {
      'default': (NyTextField textField) => textField.copyWith(
        decoration: InputDecoration(
          border: OutlineInputBorder(),
          filled: true,
          fillColor: Colors.grey[100],
        ),
      ),
      'compact': (NyTextField textField) => textField.copyWith(
        decoration: InputDecoration(
          border: UnderlineInputBorder(),
          contentPadding: EdgeInsets.symmetric(
            horizontal: 8,
            vertical: 4
          ),
        ),
      ),
    };
  }
}

Field-Level Styling

Using Style Extension

Field.email("Email",
  style: "compact".extend(
    labelText: "Email Address",
    prefixIcon: Icon(Icons.email),
    backgroundColor: Colors.grey[100],
    borderRadius: BorderRadius.circular(8),
    
    // Custom decoration states
    decoration: (data, inputDecoration) {
      return inputDecoration.copyWith(
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(8),
          borderSide: BorderSide(color: Colors.blue)
        )
      );
    },
    successDecoration: (data, inputDecoration) {
      return inputDecoration.copyWith(
        border: OutlineInputBorder(
          borderSide: BorderSide(color: Colors.green)
        )
      );
    },
    errorDecoration: (data, inputDecoration) {
      return inputDecoration.copyWith(
        border: OutlineInputBorder(
          borderSide: BorderSide(color: Colors.red)
        )
      );
    }
  )
)

Direct Styling

Field.text("Name",
  style: (NyTextField textField) => textField.copyWith(
    decoration: InputDecoration(
      prefixIcon: Icon(Icons.person),
      border: OutlineInputBorder(),
    ),
  ),
)


Advanced Features


Form Layout

fields() => [
  // Single field
  Field.text("Title"),
  
  // Grouped fields in row
  [
    Field.text("First Name"),
    Field.text("Last Name"),
  ],
  
  // Another single field
  Field.textarea("Bio")
];


Conditional Fields

Field.checkbox("Has Pets",
  onChange: (value) {
    if (value == true) {
      form.showField("Pet Names");
    } else {
      form.hideField("Pet Names");
    }
  }
)


Form Events

NyForm(
  form: form,
  onChanged: (field, data) {
    print("$field changed: $data");
  },
  validateOnFocusChange: true
)


Pre-built Components

Login Form

NyLoginForm loginForm = Forms.login(
  emailValidationMessage: "Please enter a valid email",
  passwordValidationMessage: "Password is required",
  style: "compact"
);


API Reference for NyForm

A widget that manages form state, validation, and submission in Nylo applications.

Constructor

NyForm({
  Key? key,
  required NyFormData form,
  double crossAxisSpacing = 10,
  double mainAxisSpacing = 10,
  Map<String, dynamic>? initialData,
  Function(String field, Map<String, dynamic> data)? onChanged,
  bool validateOnFocusChange = false,
  Widget? header,
  Widget? footer,
  Widget? loading,
  bool locked = false,
})

Parameters

Required Parameters

Parameter Type Description
form NyFormData The form to display and manage. Contains field definitions and validation rules.

Optional Parameters

Parameter Type Default Description
key Key? null Controls how one widget replaces another widget in the tree.
crossAxisSpacing double 10 Spacing between fields in the cross axis direction.
mainAxisSpacing double 10 Spacing between fields in the main axis direction.
initialData Map<String, dynamic>? null Initial values for form fields. Keys should match field names.
onChanged Function(String, Map<String, dynamic>)? null Callback when any field value changes. Provides field name and complete form data.
validateOnFocusChange bool false Whether to validate fields when focus changes.
header Widget? null Widget to display above the form fields.
footer Widget? null Widget to display below the form fields.
loading Widget? null Widget to display while the form is loading. Defaults to a skeleton loader if not provided.
locked bool false When true, makes the form read-only and prevents user input.

Example Usage

NyForm(
  form: LoginForm(),
  initialData: {
    "email": "user@example.com",
    "password": ""
  },
  header: Text("Login"),
  footer: SubmitButton(),
  onChanged: (field, data) {
    print("Field $field changed. New data: $data");
  },
  validateOnFocusChange: true,
  crossAxisSpacing: 16,
  mainAxisSpacing: 20,
)

Notes

  • The form parameter automatically initializes with the provided initialData if any.
  • The loading widget is only shown when the form is in a loading state.
  • The onChanged callback provides both the changed field name and the complete form data.
  • When locked is true, the form becomes non-interactive but still displays values.
  • header and footer widgets are optional and will only be displayed if provided.