Form
Pengantar
Nylo Website v7 menyediakan sistem form yang dibangun di sekitar NyFormWidget. Kelas form Anda meng-extend NyFormWidget dan merupakan widget itu sendiri -- tidak perlu wrapper terpisah. Form mendukung validasi bawaan, banyak tipe field, styling, dan manajemen data.
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: "..."}
},
)
Membuat Form
Gunakan Metro CLI untuk membuat form baru:
metro make:form LoginForm
Ini membuat 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');
}
Form meng-extend NyFormWidget dan meng-override method fields() untuk mendefinisikan field form. Setiap field menggunakan konstruktor bernama seperti Field.text(), Field.email(), atau Field.password(). Getter static NyFormActions get actions menyediakan cara mudah untuk berinteraksi dengan form dari mana saja di aplikasi Anda.
Menampilkan Form
Karena kelas form Anda meng-extend NyFormWidget, ia merupakan widget itu sendiri. Gunakan langsung di widget tree Anda:
@override
Widget view(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LoginForm(
submitButton: Button.primary(text: "Submit"),
onSubmit: (data) {
print(data);
},
),
),
);
}
Mengirim Form
Ada tiga cara untuk mengirim form:
Menggunakan onSubmit dan submitButton
Kirim onSubmit dan submitButton saat membuat form. Nylo Website menyediakan tombol bawaan yang berfungsi sebagai tombol submit:
LoginForm(
submitButton: Button.primary(text: "Submit"),
onSubmit: (data) {
print(data); // {email: "...", password: "..."}
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
)
Gaya tombol yang tersedia: Button.primary, Button.secondary, Button.outlined, Button.textOnly, Button.icon, Button.gradient, Button.rounded, Button.transparency.
Menggunakan NyFormActions
Gunakan getter actions untuk mengirim dari mana saja:
LoginForm.actions.submit(
onSuccess: (data) {
print(data);
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
showToastError: true,
);
Menggunakan method statis NyFormWidget.submit()
Kirim form berdasarkan namanya dari mana saja:
NyFormWidget.submit("LoginForm",
onSuccess: (data) {
print(data);
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
showToastError: true,
);
Saat dikirim, form memvalidasi semua field. Jika valid, onSuccess dipanggil dengan Map<String, dynamic> data field (kunci adalah versi snake_case dari nama field). Jika tidak valid, error toast ditampilkan secara default dan onFailure dipanggil jika disediakan.
Tipe Field
Nylo Website v7 menyediakan 22 tipe field melalui konstruktor bernama pada kelas Field. Semua konstruktor field berbagi parameter umum ini:
| Parameter | Tipe | Default | Deskripsi |
|---|---|---|---|
key |
String |
Wajib | Identifier field (posisional) |
label |
String? |
null |
Label tampilan kustom (default ke key dalam title case) |
value |
dynamic |
null |
Nilai awal |
validator |
FormValidator? |
null |
Aturan validasi |
autofocus |
bool |
false |
Auto-fokus saat dimuat |
dummyData |
String? |
null |
Data tes/pengembangan |
header |
Widget? |
null |
Widget ditampilkan di atas field |
footer |
Widget? |
null |
Widget ditampilkan di bawah field |
titleStyle |
TextStyle? |
null |
Gaya teks label kustom |
hidden |
bool |
false |
Sembunyikan field |
readOnly |
bool? |
null |
Buat field hanya-baca |
style |
FieldStyle? |
Bervariasi | Konfigurasi gaya khusus field |
onChanged |
Function(dynamic)? |
null |
Callback perubahan nilai |
Field Teks
Field.text("Name")
Field.text("Name",
value: "John",
validator: FormValidator.notEmpty(),
autofocus: true,
)
Tipe gaya: FieldStyleTextField
Field Angka
Field.number("Age")
// Decimal numbers
Field.number("Score", decimal: true)
Parameter decimal mengontrol apakah input desimal diizinkan. Tipe gaya: FieldStyleTextField
Field Password
Field.password("Password")
// With visibility toggle
Field.password("Password", viewable: true)
Parameter viewable menambahkan toggle tampilkan/sembunyikan. Tipe gaya: FieldStyleTextField
Field Email
Field.email("Email", validator: FormValidator.email())
Secara otomatis mengatur tipe keyboard email dan memfilter spasi. Tipe gaya: FieldStyleTextField
Field URL
Field.url("Website", validator: FormValidator.url())
Mengatur tipe keyboard URL. Tipe gaya: FieldStyleTextField
Field Area Teks
Field.textArea("Description")
Input teks multi-baris. Tipe gaya: FieldStyleTextField
Field Nomor Telepon
Field.phoneNumber("Mobile Phone")
Secara otomatis memformat input nomor telepon. Tipe gaya: FieldStyleTextField
Kapitalisasi Kata
Field.capitalizeWords("Full Name")
Mengkapitalkan huruf pertama setiap kata. Tipe gaya: FieldStyleTextField
Kapitalisasi Kalimat
Field.capitalizeSentences("Bio")
Mengkapitalkan huruf pertama setiap kalimat. Tipe gaya: FieldStyleTextField
Field Tanggal
Field.date("Birthday")
Field.date("Birthday",
dummyData: "1990-01-01",
style: FieldStyleDateTimePicker(
firstDate: DateTime(1900),
lastDate: DateTime.now(),
),
)
Membuka date picker. Tipe gaya: FieldStyleDateTimePicker
Field Tanggal Waktu
Field.datetime("Check in Date")
Field.datetime("Appointment", dummyData: "2025-01-01 10:00")
Membuka picker tanggal dan waktu. Tipe gaya: FieldStyleDateTimePicker
Field Input Masked
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
)
Karakter # dalam mask digantikan oleh input pengguna. Gunakan match untuk mengontrol karakter yang diizinkan. Ketika maskReturnValue adalah true, nilai yang dikembalikan mencakup format mask.
Field Mata Uang
Field.currency("Price", currency: "usd")
Parameter currency wajib dan menentukan format mata uang. Tipe gaya: FieldStyleTextField
Field 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"),
)
Tipe gaya: FieldStyleCheckbox
Field Switch Box
Field.switchBox("Enable Notifications")
Tipe gaya: FieldStyleSwitchBox
Field 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",
}),
)
Parameter options memerlukan FormCollection (bukan list mentah). Lihat FormCollection untuk detail. Tipe gaya: FieldStylePicker
Field Radio
Field.radio("Newsletter",
options: FormCollection.fromMap({
"Yes": "Yes, I want to receive newsletters",
"No": "No, I do not want to receive newsletters",
}),
)
Parameter options memerlukan FormCollection. Tipe gaya: FieldStyleRadio
Field 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",
}),
)
Memungkinkan multi-seleksi melalui widget chip. Parameter options memerlukan FormCollection. Tipe gaya: FieldStyleChip
Field 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,
),
)
Tipe gaya: FieldStyleSlider -- konfigurasikan min, max, divisions, warna, tampilan nilai, dan lainnya.
Field Range Slider
Field.rangeSlider("Price Range",
style: FieldStyleRangeSlider(
min: 0,
max: 1000,
divisions: 20,
activeColor: Colors.blue,
inactiveColor: Colors.grey,
),
)
Mengembalikan objek RangeValues. Tipe gaya: FieldStyleRangeSlider
Field Kustom
Gunakan Field.custom() untuk menyediakan widget stateful Anda sendiri:
Field.custom("My Field",
child: MyCustomFieldWidget(),
)
Parameter child memerlukan widget yang meng-extend NyFieldStatefulWidget. Ini memberi Anda kontrol penuh atas rendering dan perilaku field.
Field Widget
Gunakan Field.widget() untuk menyematkan widget apa pun di dalam form tanpa menjadi field form:
Field.widget(child: Divider())
Field.widget(child: Text("Section Header", style: TextStyle(fontSize: 18)))
Field widget tidak berpartisipasi dalam validasi atau pengumpulan data. Mereka murni untuk tata letak.
FormCollection
Field picker, radio, dan chip memerlukan FormCollection untuk opsi mereka. FormCollection menyediakan antarmuka terpadu untuk menangani format opsi yang berbeda.
Membuat 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() secara otomatis mendeteksi format data dan mendelegasikan ke konstruktor yang sesuai.
FormOption
Setiap opsi dalam FormCollection adalah FormOption dengan properti value dan label:
FormOption option = FormOption(value: "us", label: "United States");
print(option.value); // "us"
print(option.label); // "United States"
Mengkueri Opsi
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"]
Validasi Form
Tambahkan validasi ke field apa pun menggunakan parameter validator dengan 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;
},
)
)
Saat form dikirim, semua validator diperiksa. Jika ada yang gagal, error toast menampilkan pesan error pertama dan callback onFailure dipanggil.
Lihat juga: Untuk daftar lengkap validator yang tersedia, lihat halaman Validasi.
Mengelola Data Form
Data Awal
Ada dua cara untuk mengatur data awal pada form.
Opsi 1: Override getter init di kelas form Anda
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');
}
Getter init dapat mengembalikan Map sinkron atau Future<Map> async. Kunci dicocokkan dengan nama field menggunakan normalisasi snake_case, jadi "First Name" dipetakan ke field dengan kunci "First Name".
Opsi 2: Kirim initialData ke widget form
EditAccountForm(
initialData: {
"first_name": "John",
"last_name": "Doe",
},
)
Mengatur Nilai Field
Gunakan NyFormActions untuk mengatur nilai field dari mana saja:
// Set a single field value
EditAccountForm.actions.updateField("First Name", "Jane");
Mengatur Opsi Field
Perbarui opsi pada field picker, chip, atau radio secara dinamis:
EditAccountForm.actions.setOptions("Category", FormCollection.from(["New Option 1", "New Option 2"]));
Membaca Data Form
Data form diakses melalui callback onSubmit saat form dikirim, atau melalui callback onChanged untuk pembaruan real-time:
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");
},
)
Menghapus Data
// Clear all fields
EditAccountForm.actions.clear();
// Clear a specific field
EditAccountForm.actions.clearField("First Name");
Memperbarui Field
// 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();
Tombol Submit
Kirim submitButton dan callback onSubmit saat membuat form:
UserInfoForm(
submitButton: Button.primary(text: "Submit"),
onSubmit: (data) {
print(data);
},
onFailure: (errors) {
print(errors.first.rule.getMessage());
},
)
submitButton secara otomatis ditampilkan di bawah field form. Anda dapat menggunakan gaya tombol bawaan atau widget kustom.
Anda juga dapat menggunakan widget apa pun sebagai tombol submit dengan mengirimnya sebagai footer:
UserInfoForm(
onSubmit: (data) {
print(data);
},
footer: ElevatedButton(
onPressed: () {
UserInfoForm.actions.submit(
onSuccess: (data) {
print(data);
},
);
},
child: Text("Submit"),
),
)
Tata Letak Form
Tempatkan field berdampingan dengan membungkusnya dalam 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"),
];
Field dalam List dirender dalam Row dengan lebar Expanded yang sama. Jarak antar field dikontrol oleh parameter crossAxisSpacing pada NyFormWidget.
Visibilitas Field
Tampilkan atau sembunyikan field secara programatis menggunakan method hide() dan show() pada Field. Anda dapat mengakses field di dalam kelas form Anda atau melalui callback onChanged:
// Inside your NyFormWidget subclass or onChanged callback
Field nameField = ...;
// Hide the field
nameField.hide();
// Show the field
nameField.show();
Field tersembunyi tidak dirender di UI tetapi masih ada dalam daftar field form.
Styling Field
Setiap tipe field memiliki subkelas FieldStyle yang sesuai untuk styling:
| Tipe Field | Kelas Gaya |
|---|---|
| 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 |
Kirim objek gaya ke parameter style dari field apa pun:
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,
),
)
Method Statis NyFormWidget
NyFormWidget menyediakan method statis untuk berinteraksi dengan form berdasarkan nama dari mana saja di aplikasi Anda:
| Method | Deskripsi |
|---|---|
NyFormWidget.submit(name, onSuccess:, onFailure:, showToastError:) |
Kirim form berdasarkan namanya |
NyFormWidget.stateRefresh(name) |
Refresh state UI form |
NyFormWidget.stateSetValue(name, key, value) |
Atur nilai field berdasarkan nama form |
NyFormWidget.stateSetOptions(name, key, options) |
Atur opsi field berdasarkan nama form |
NyFormWidget.stateClearData(name) |
Hapus semua field berdasarkan nama form |
NyFormWidget.stateRefreshForm(name) |
Refresh field form (memanggil ulang 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");
Tips: Lebih baik gunakan
NyFormActions(lihat di bawah) daripada memanggil method statis ini secara langsung -- lebih ringkas dan minim kesalahan.
Referensi Konstruktor NyFormWidget
Saat meng-extend NyFormWidget, ini adalah parameter konstruktor yang dapat Anda kirim:
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
)
Callback onChanged menerima Field yang berubah dan nilai barunya:
LoginForm(
onChanged: (Field field, dynamic value) {
print("${field.key} changed to: $value");
},
)
NyFormActions
NyFormActions menyediakan cara mudah untuk berinteraksi dengan form dari mana saja di aplikasi Anda. Definisikan sebagai getter statis di kelas form Anda:
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');
}
Aksi yang Tersedia
| Method | Deskripsi |
|---|---|
actions.updateField(key, value) |
Atur nilai field |
actions.clearField(key) |
Hapus field tertentu |
actions.clear() |
Hapus semua field |
actions.refresh() |
Refresh state UI form |
actions.refreshForm() |
Panggil ulang fields() dan rebuild |
actions.setOptions(key, options) |
Atur opsi pada field picker/chip/radio |
actions.submit(onSuccess:, onFailure:, showToastError:) |
Kirim dengan validasi |
// 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 NyFormWidget
Method yang dapat Anda override di subkelas NyFormWidget Anda:
| Override | Deskripsi |
|---|---|
fields() |
Definisikan field form (wajib) |
init |
Sediakan data awal (sinkron atau async) |
onChange(field, data) |
Tangani perubahan field secara internal |
Referensi Semua Tipe Field
| Konstruktor | Parameter Kunci | Deskripsi |
|---|---|---|
Field.text() |
-- | Input teks standar |
Field.email() |
-- | Input email dengan tipe keyboard |
Field.password() |
viewable |
Password dengan toggle visibilitas opsional |
Field.number() |
decimal |
Input numerik, desimal opsional |
Field.currency() |
currency (wajib) |
Input berformat mata uang |
Field.capitalizeWords() |
-- | Input teks title case |
Field.capitalizeSentences() |
-- | Input teks sentence case |
Field.textArea() |
-- | Input teks multi-baris |
Field.phoneNumber() |
-- | Nomor telepon terformat otomatis |
Field.url() |
-- | Input URL dengan tipe keyboard |
Field.mask() |
mask (wajib), match, maskReturnValue |
Input teks masked |
Field.date() |
-- | Date picker |
Field.datetime() |
-- | Picker tanggal dan waktu |
Field.checkbox() |
-- | Checkbox boolean |
Field.switchBox() |
-- | Toggle switch boolean |
Field.picker() |
options (wajib FormCollection) |
Seleksi tunggal dari daftar |
Field.radio() |
options (wajib FormCollection) |
Grup tombol radio |
Field.chips() |
options (wajib FormCollection) |
Chip multi-seleksi |
Field.slider() |
-- | Slider nilai tunggal |
Field.rangeSlider() |
-- | Slider nilai rentang |
Field.custom() |
child (wajib NyFieldStatefulWidget) |
Widget stateful kustom |
Field.widget() |
child (wajib Widget) |
Sematkan widget apa pun (non-field) |