Forms
- Introduction
- Creating a form
- Displaying a form
- Submitting a Form
- Managing Data
- Form
- Pre-built Forms
- Form Parameters
- Listening to Form Changes
- Custom Form Cast
Introduction to Forms
Forms are fundamental for any modern mobile or web application.
In Nylo you can use the NyForm
class to manage, validate and submit data all in one place.
It's extremely easy and customizable, let's take a look at how to create a form.
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",
cast: FormCast.email(),
validator: FormValidator.email()
),
Field("Password",
cast: FormCast.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';
...
// Create your form
LoginForm form = LoginForm();
// add the form using the NyForm widget
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
shrinkWrap: true,
children: [
NyForm(form: form),
ElevatedButton(child: Text("Submit"),
onPressed: () {
form.submit(onSuccess: (data) {
// data = { "Email": "example@mail.com", "Password": "password" }
});
},
)
],
),
);
}
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.
Submitting a form
To submit a form, you can call the submit
method on a form.
LoginForm form = LoginForm();
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
shrinkWrap: true,
children: [
NyForm(form: form),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
form.submit(onSuccess: (data) {
// Do something with the data
});
},
child: Icon(Icons.save),
),
);
}
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 a form
The easiest way to create a form is with Metro
.
You can create a form by running the following command:
dart run nylo_framework:main make:form AdvertForm
# or with Metro alias
metro make:form AdvertForm
This will create a new form class in the lib/app/forms
directory.
import 'package:nylo_framework/nylo_framework.dart';
class AdvertForm extends NyFormData {
AdvertForm({String? name}) : super(name ?? "advert");
// Add your fields here
@override
fields() => [
Field("Name",
validator: FormValidator.rule("not_empty|max:20")
),
Field("Price",
cast: FormCast.currency("usd"),
validator: FormValidator.rule("not_empty")
),
Field("Favourite Color",
value: "Blue",
cast: FormCast.picker(
options: ["Red", "Green", "Blue", "Yellow"]
),
validator: FormValidator.rule("not_empty")
),
];
}
Displaying a form
To display a form, you can use the NyForm
widget.
Once you have created a form, you can display it anywhere in your application.
AdvertForm form = AdvertForm();
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
NyForm(form: form),
],
),
);
}
// or like this
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
form.create()
],
),
);
}
This will display the fields associated with the form.
You'll need to provide an additional widget to submit the form using form.submit()
.
Submitting a Form
To submit a form, you can call the submit
method on a form.
AdvertForm form = AdvertForm();
@override
Widget build(BuildContext context) {
return Scaffold(
body: NyForm(form: form),
floatingActionButton: FloatingActionButton(
onPressed: () {
form.submit(onSuccess: (data) {
// Do something with the data
});
},
child: Icon(Icons.save),
),
);
}
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.
You can also use the onFailure: (error) {}
callback to handle form validation errors.
AdvertForm form = AdvertForm();
@override
Widget build(BuildContext context) {
return Scaffold(
body: NyForm(form: form),
floatingActionButton: FloatingActionButton(
onPressed: () {
form.submit(
onSuccess: (data) {
// Do something with the data
},
onFailure: (error) {
// Do something with the error
}
);
},
child: Icon(Icons.save),
),
);
}
Setting data
You can set data using the initialData
parameter.
CarAdvertForm form = CarAdvertForm();
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
NyForm(form: form, initialData: {
"Model": "BMW",
"Price": 45000,
"Wheel Size": "18",
}),
],
)
);
}
// or like this
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
form.create(initialData: {
"Model": "BMW",
"Price": 45000,
"Wheel Size": "18",
}),
],
)
);
}
If you need to set data after it's been created, use the setData
method or setField
method.
-
setData
will set all the data at once. -
setField
will set the data for a specific field.
CarAdvertForm form = CarAdvertForm();
init() async {
form.setData({
"Model": "BMW",
"Price": 45000,
"Wheel Size": "18",
});
// or by field name
form.setField("Model", "Mercedes");
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
NyForm(form: form),
ElevatedButton(
onPressed: () {
form.submit(onSuccess: (data) {
// Do something with the data
// data = { "Model": "Mercedes", "Price": 45000, "Wheel Size": "18" }
});
},
child: Text("Submit"),
),
],
)
);
}
Clearing Data
If you need to clear all the data, you can use the clear
method.
JobForm form = JobForm();
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
NyForm(form: form),
ElevatedButton(child: Text("Clear Data"),
onPressed: () {
form.clear();
},
),
],
)
);
}
If you need to clear a specific field, you can use the clearField
method.
form.clearField("Name");
Fields
The Field
class in Nylo is important because it defines the fields in your form.
Let's imagine we run a social media app and we have a page where users can upload a 'post'.
The data we need to collect is the title, category, description, and hash tags.
class PostForm extends NyFormData {
PostForm({String? name}) : super(name ?? "post");
@override
fields() => [
Field("Title"),
Field("Category"),
Field("Description"),
Field("Hash Tags"),
];
}
The above code will create a form with three fields: Title
, Category
, Description
, and Hash Tags
.
We can improve it by adding casts and validation rules.
class PostForm extends NyFormData {
PostForm({String? name}) : super(name ?? "post");
@override
fields() => [
Field("Title",
cast: FormCast.capitalizeWords(),
validate: FormValidator()
.notEmpty()
.maxLength(50),
),
Field("Category",
cast: FormCast.picker(
options: ["Technology", "Fashion", "Food", "Travel"],
),
validate: FormValidator()
.contains(["Technology", "Fashion", "Food", "Travel"]),
),
Field("Description",
cast: FormCast.textArea(),
validate: FormValidator(message: "Please write a description")
.notEmpty(),
),
Field("Hash Tags",
cast: FormCast.textArea(textAreaSize: TextAreaSize.md),
validate: FormValidator.rule("not_empty")
),
];
}
This new form now has casts and validation rules.
When the form is submitted, Nylo will validate the form and if the form is valid, it will call the onSuccess
callback with the form data.
Grouping Fields
If you want to group fields next to each other, you can use a List
.
It's easy, here's an example:
class PostForm extends NyFormData {
PostForm({String? name}) : super(name ?? "post");
@override
fields() => [
[
Field("Title",
cast: FormCast.capitalizeWords(),
),
Field("Category",
cast: FormCast.picker(
options: ["Technology", "Fashion", "Food", "Travel"],
),
),
],
Field("Description",
cast: FormCast.textArea(),
),
Field("Hash Tags",
cast: FormCast.textArea(textAreaSize: TextAreaSize.md),
),
];
}
If you try this, you will see that the Title
and Category
fields are grouped together.
Behind the scenes, Nylo will create a Row widget to display the fields.
You can also adjust the mainAxisSpacing and crossAxisSpacing using the mainAxisSpacing
and crossAxisSpacing
parameters on the NyForm
widget.
Validation
Nylo is able to validate your form fields using the FormValidator
class.
Here's all the available validation rules:
Method | Description |
---|---|
FormValidator.email() |
Validates an email address |
FormValidator.password() |
Validates a password |
FormValidator().notEmpty() |
Validates that the field is not empty |
FormValidator().minLength(5) |
Validates that the field has a minimum length of 5 |
FormValidator().maxLength(10) |
Validates that the field has a maximum length of 10 |
FormValidator().contains(["developer", "manager"]) |
Validates that the field contains one of the values in the array |
FormValidator().numeric() |
Validates that the field is a number |
FormValidator().date() |
Validates that the field is a date |
FormValidator().uppercase() |
Validates that the field is uppercase |
FormValidator().lowercase() |
Validates that the field is lowercase |
FormValidator().regex(r"^[a-zA-Z0-9]*$") |
Validates that the field matches a regex pattern |
FormValidator().dateInPast() |
Validates that the date is in the past |
FormValidator().dateInFuture() |
Validates that the date is in the future |
FormValidator().isTrue() |
Validates that the value is true |
FormValidator().isFalse() |
Validates that the value is false |
FormValidator().dateAgeIsYounger(35) |
Validates that the date is younger than 35 |
FormValidator().dateAgeIsOlder(35) |
Validates that the date is older than 35 |
FormValidator().zipcodeUs() |
Validates that the value is a valid US zipcode |
FormValidator().postcodeUk() |
Validates that the value is a valid UK postcode |
If you need to create a custom validation rule, you can do so by calling FormValidator.custom
like in the example below.
class JobForm extends NyFormData {
@override
fields() => [
Field("Name", validation: FormValidator().notEmpty().maxLength(20)),
Field("Age", validation: FormValidator.rule("not_empty|numeric|min:18")),
Field("Salary", validation: FormValidator.custom((value) {
if (value < 10000) {
return false;
}
return true;
}, message: "Salary must be greater than 10000")),
];
}
In the above example, we have defined validation rules for the fields Name
, Age
and Salary
.
If the form is invalid, the onFailure
callback will be called with the error message.
You can find all the available validation rules here.
Validate on focus change
This is feature is disabled by default. If you want to enable it, you can do so by setting the validateOnFocusChange
parameter to true
.
NyForm(form: accountForm, validateOnFocusChange: true),
Now, the form will only validate the field when the focus changes.
Custom Error Messages
You can also provide a custom error message for each field.
class JobForm extends NyFormData {
@override
fields() => [
Field("Name",
validation: FormValidator(message: "Name is required").notEmpty().maxLength(20)
),
Field("Age",
validation: FormValidator.rule("not_empty|numeric|min:25", message: "Age must be a number and greater than 25")
),
Field("Salary", validation: FormValidator.custom((value) {
if (value < 10000) {
return false;
}
return true;
}, message: "Salary must be greater than 10000")),
];
}
Casts
Casts are used to cast the fields to their respective types.
class JobForm extends NyFormData {
@override
fields() => [
Field("Age",
cast: FormCast.number()
),
Field("Salary",
cast: FormCast.currency("usd"),
),
Field("Position",
cast: FormCast.picker(
options: ["Developer", "Manager", "Designer", "Tester"]
),
),
];
}
Available Casts
Method | Description |
---|---|
FormCast() |
Default cast |
FormCast.email() |
Cast to email |
FormCast.password() |
Cast to password |
FormCast.password(viewable: true) |
Cast to viewable password |
FormCast.currency("usd") |
Cast to currency (supports multiple currencies) |
FormCast.picker() |
Cast to picker |
FormCast.textArea() |
Cast to textarea |
FormCast.textArea(textAreaSize: TextAreaSize.md) |
Cast to medium textarea |
FormCast.capitalizeWords() |
Cast to capitalize words |
FormCast.capitalizeSentences() |
Cast to capitalize sentences |
FormCast.number() |
Cast to number |
FormCast.phoneNumber() |
Cast to phone number |
FormCast.datetime() |
Cast to datetime |
FormCast.uppercase() |
Cast to uppercase |
FormCast.lowercase() |
Cast to lowercase |
You can use any of the above casts in your form.
If you'd like to create your own custom cast, you can do so by creating a new class in config/form_casts.dart
.
FormCast.currency - all currencies
The FormCast.currency
cast supports multiple currencies, here's a list of all the available currencies:
Currency Code | Currency Name |
---|---|
usd |
US Dollar |
eur |
Euro |
gbp |
British Pound Sterling |
jpy |
Japanese Yen |
cny |
Chinese Yuan |
cad |
Canadian Dollar |
aud |
Australian Dollar |
inr |
Indian Rupee |
idr |
Indonesian Rupiah |
sgd |
Singapore Dollar |
myr |
Malaysian Ringgit |
thb |
Thai Baht |
twd |
New Taiwan Dollar |
vnd |
Vietnamese Dong |
zar |
South African Rand |
pkr |
Pakistani Rupee |
Dummy Data
You can add dummy data to your form when you are developing your application.
If your .env
file is set to production, the dummy data will be ignored.
Example
APP_ENV="developing" # or "production"
class FitnessLevelForm extends NyFormData {
@override
fields() => [
Field("Name",
cast: FormCast.number(),
dummyData: "John Doe"
),
Field("Weight",
dummyData: "75 kg"
),
Field("Height",
dummyData: "180 cm"
),
Field("Hash Tags",
dummyData: "#fitness, #health, #gym"
),
];
}
In the above example, the fields Name
, Weight
, Height
, and Hash Tags
will have dummy data when the form is displayed.
Note: Remove to update your .env file to
APP_ENV="production"
to disable dummy data.
Style
Nylo provides two ways to style your form fields.
The first is by defining a global style for all fields in the form. The second is by styling individual fields.
Global Style
Global styles are the easiest way to customize your form fields. You can define the style for any field except for the Picker
and DateTime
field.
To get started, first navigate to your /app/forms/style/form_style.dart
file.
This file will contain a class called FormStyle
, you'll be able to define all your global styles here.
To update the TextField's, override the textField
method. This function is responsible for styling the TextField's in your form.
import 'package:flutter/material.dart';
import 'package:nylo_framework/nylo_framework.dart';
class FormStyle extends NyFormStyle {
/// TextField styles for the form
@override
FormStyleTextField textField(BuildContext context, Field field) {
return {
'default': (NyTextField textField) => textField.copyWith(
decoration: InputDecoration(
border: InputBorder.none,
filled: true,
fillColor: Colors.blue.shade100,
labelText: field.name,
),
),
};
}
...
In the above example, we have defined a global style for all the TextField's in the form.
Note that the default
key is required to define the default style for all the TextField's.
You can also define more styles, like in the example below:
``` dart
import 'package:flutter/material.dart';
import 'package:nylo_framework/nylo_framework.dart';
class FormStyle extends NyFormStyle {
/// TextField styles for the form
@override
FormStyleTextField textField(BuildContext context, Field field) {
return {
'default': (NyTextField textField) => textField.copyWith(
decoration: InputDecoration(
border: InputBorder.none,
filled: true,
fillColor: Colors.blue.shade100,
labelText: field.name,
),
),
'minimal': (NyTextField textField) => textField.copyWith(
decoration: InputDecoration(
...
),
),
};
}
...
Now, you can use the minimal
style in your form.
class ProfileForm extends NyFormData {
fields() => [
Field("Name", style: "minimal"),
Field("Username"),
];
}
You can also use the style
parameter, this will override the global style for the field.
Nylo contains a compact
style that you can use in your form.
class RegisterForm extends NyFormData {
@override
fields() => [
Field("Name",
style: "compact"
),
Field("Email",
style: "compact"
),
Field("Password",
style: "compact"
),
Field("Confirm Password",
style: "compact"
),
];
}
You can also use the style
parameter to perform an inline style override.
class RegisterForm extends NyFormData {
@override
fields() => [
Field("Name",
style: (NyTextField nyTextField) => nyTextField.copyWith(
decoration: InputDecoration(
labelText: "Name",
hintText: "Enter your name",
),
),
),
Field("Email",
style: (NyTextField nyTextField) => nyTextField.copyWith(
decoration: InputDecoration(
labelText: "Email",
hintText: "Enter your email",
),
),
),
Field("Password",
style: (NyTextField nyTextField) => nyTextField.copyWith(
decoration: InputDecoration(
labelText: "Password",
hintText: "Enter your password",
),
),
),
Field("Confirm Password",
style: (NyTextField nyTextField) => nyTextField.copyWith(
decoration: InputDecoration(
labelText: "Confirm Password",
hintText: "Confirm your password",
),
),
),
];
}
Pre-built Forms
Nylo comes with pre-built forms that you can use out of the box.
This list will be updated as more forms are added.
NyLoginForm
The NyLoginForm
is a pre-built login form that you can use in your application.
Fields
- Email -
FormCast.email()
- Password -
FormCast.password()
@override
Widget build(BuildContext context) {
return Scaffold(
body: NyForm.login(name: "login"),
floatingActionButton: FloatingActionButton(
onPressed: () {
NyForm.submit("login", onSuccess: (data) {
// Do something with the data
});
},
child: Icon(Icons.save),
),
);
}
NyForm Parameters
-
key
: The key of the form -
form
: The form to display -
initialData
: The initial data for the form -
crossAxisSpacing
: The cross axis spacing (default: 10) -
mainAxisSpacing
: The main axis spacing (default: 10) -
onChanged
: The callback when the form changes -
validateOnFocusChange
: Validate the form on focus change (default: false) -
locked
: Lock the form (default: false)
Listening to Form Changes
You can listen to form changes using the onChanged
callback.
@override
Widget build(BuildContext context) {
return Scaffold(
body: NyForm(
form: form,
onChanged: (data) {
// Do something with the data
// data = { "Name": "John Doe", "Age": 25, "Salary": 45000, "Position": "Developer" }
},
),
);
}
Custom Form Casts
If you want to use custom form casts, you can create them and reuse them in your forms.
First, make sure your AppProvider
is set up correctly.
class AppProvider implements NyProvider {
@override
boot(Nylo nylo) async {
...
nylo.addFormCasts(formCasts); // Add the form casts
}
Inside config/form_casts.dart
, you can add your custom form casts.
import 'package:nylo_framework/nylo_framework.dart';
final Map<String, dynamic> formCasts = {
/// Example
"age_picker": (Field field, Function(dynamic value)? onChanged) {
return MyCustomField(field, onChanged);
},
};
Your custom form cast needs to accept the following parameters:
-
Field field
: The field to cast -
Function(dynamic value)? onChanged
: The callback when the field changes
Nylo will automatically register the form casts for you.
Now, you can use your custom form cast in your form.
class JobForm extends NyFormData {
@override
fields() => [
Field("Age",
cast: "age_picker"
),
Field("Salary",
cast: FormCast.currency("usd"),
),
Field("Position",
cast: FormCast.picker(
options: ["Developer", "Manager", "Designer", "Tester"]
),
),
];
}