# Validation

<div id="introduction"></div>

## Giới thiệu

Nylo v7 cung cấp hệ thống validation được xây dựng xung quanh class `FormValidator`. Bạn có thể xác thực dữ liệu bên trong trang bằng phương thức `check()`, hoặc sử dụng `FormValidator` trực tiếp cho validation độc lập và cấp trường.

``` dart
// Xác thực dữ liệu trong NyPage/NyState bằng check()
check((validate) {
  validate.that('user@example.com').email();
  validate.that('Anthony')
              .capitalized()
              .maxLength(50);
}, onSuccess: () {
  print("Tất cả validation đã thông qua!");
});

// FormValidator với các trường biểu mẫu
Field.text("Email", validator: FormValidator.email())
```

<div id="validating-data"></div>

## Xác thực dữ liệu với check()

Bên trong bất kỳ `NyPage` nào, sử dụng phương thức `check()` để xác thực dữ liệu. Nó nhận callback nhận danh sách validators. Sử dụng `.that()` để thêm dữ liệu và chuỗi quy tắc:

``` dart
class _RegisterPageState extends NyPage<RegisterPage> {

  void handleForm() {
    check((validate) {
      validate.that(_emailController.text, label: "Email").email();
      validate.that(_passwordController.text, label: "Password")
          .notEmpty()
          .password(strength: 2);
    }, onSuccess: () {
      // Tất cả validation đã thông qua
      _submitForm();
    }, onValidationError: (FormValidationResponseBag bag) {
      // Validation thất bại
      print(bag.firstErrorMessage);
    });
  }
}
```

### Cách check() hoạt động

1. `check()` tạo một `List<FormValidator>` trống
2. Callback của bạn sử dụng `.that(data)` để thêm `FormValidator` mới với dữ liệu vào danh sách
3. Mỗi `.that()` trả về `FormValidator` bạn có thể chuỗi quy tắc
4. Sau callback, mọi validator trong danh sách được kiểm tra
5. Kết quả được thu thập vào `FormValidationResponseBag`

### Xác thực nhiều trường

``` dart
check((validate) {
  validate.that(_nameController.text, label: "Name").notEmpty().capitalized();
  validate.that(_emailController.text, label: "Email").email();
  validate.that(_phoneController.text, label: "Phone").phoneNumberUs();
  validate.that(_ageController.text, label: "Age").numeric().minValue(18);
}, onSuccess: () {
  _submitForm();
});
```

Tham số `label` tùy chọn đặt tên dễ đọc được sử dụng trong thông báo lỗi (ví dụ: "The Email must be a valid email address.").

<div id="validation-results"></div>

## Kết quả Validation

Phương thức `check()` trả về `FormValidationResponseBag` (một `List<FormValidationResult>`), bạn cũng có thể kiểm tra trực tiếp:

``` dart
FormValidationResponseBag bag = check((validate) {
  validate.that(_emailController.text, label: "Email").email();
  validate.that(_passwordController.text, label: "Password")
      .password(strength: 1);
});

if (bag.isValid) {
  print("Tất cả hợp lệ!");
} else {
  // Lấy thông báo lỗi đầu tiên
  String? error = bag.firstErrorMessage;
  print(error);

  // Lấy tất cả kết quả thất bại
  List<FormValidationResult> errors = bag.validationErrors;

  // Lấy tất cả kết quả thành công
  List<FormValidationResult> successes = bag.validationSuccess;
}
```

### FormValidationResult

Mỗi `FormValidationResult` đại diện cho kết quả xác thực một giá trị:

``` dart
FormValidator validator = FormValidator(data: "test@email.com")
    .email()
    .maxLength(50);

FormValidationResult result = validator.check();

if (result.isValid) {
  print("Hợp lệ!");
} else {
  // Thông báo lỗi đầu tiên
  String? message = result.getFirstErrorMessage();

  // Tất cả thông báo lỗi
  List<String> messages = result.errorMessages();

  // Các phản hồi lỗi
  List<FormValidationError> errors = result.errorResponses;
}
```

<div id="using-form-validator"></div>

## Sử dụng FormValidator

`FormValidator` có thể được sử dụng độc lập hoặc với form fields.

### Sử dụng độc lập

``` dart
// Sử dụng constructor được đặt tên
FormValidator validator = FormValidator.email();
FormValidationResult result = validator.check("user@example.com");

if (result.isValid) {
  print("Email hợp lệ");
} else {
  String? errorMessage = result.getFirstErrorMessage();
  print("Lỗi: $errorMessage");
}
```

### Với dữ liệu trong Constructor

``` dart
FormValidator validator = FormValidator(data: "user@example.com", attribute: "Email")
    .email()
    .maxLength(50);

FormValidationResult result = validator.check();
print(result.isValid); // true
```

<div id="form-validator-named-constructors"></div>

## Constructor được đặt tên của FormValidator

Nylo v7 cung cấp các constructor được đặt tên cho validation phổ biến:

``` dart
// Validation email
FormValidator.email(message: "Vui lòng nhập email hợp lệ")

// Validation mật khẩu (độ mạnh 1 hoặc 2)
FormValidator.password(strength: 1)
FormValidator.password(strength: 2, message: "Mật khẩu quá yếu")

// Số điện thoại
FormValidator.phoneNumberUk()
FormValidator.phoneNumberUs()

// Validation URL
FormValidator.url()

// Ràng buộc độ dài
FormValidator.minLength(5, message: "Quá ngắn")
FormValidator.maxLength(100, message: "Quá dài")

// Ràng buộc giá trị
FormValidator.minValue(18, message: "Phải từ 18 trở lên")
FormValidator.maxValue(100)

// Ràng buộc kích thước (cho danh sách/chuỗi)
FormValidator.minSize(2, message: "Chọn ít nhất 2")
FormValidator.maxSize(5)

// Không rỗng
FormValidator.notEmpty(message: "Trường này là bắt buộc")

// Chứa giá trị
FormValidator.contains(['option1', 'option2'])

// Bắt đầu/kết thúc bằng
FormValidator.beginsWith("https://")
FormValidator.endsWith(".com")

// Kiểm tra boolean
FormValidator.booleanTrue(message: "Phải chấp nhận điều khoản")
FormValidator.booleanFalse()

// Số
FormValidator.numeric()

// Validation ngày
FormValidator.date()
FormValidator.dateInPast()
FormValidator.dateInFuture()
FormValidator.dateAgeIsOlder(18, message: "Phải từ 18 tuổi trở lên")
FormValidator.dateAgeIsYounger(65)

// Kiểu chữ
FormValidator.uppercase()
FormValidator.lowercase()
FormValidator.capitalized()

// Định dạng địa chỉ
FormValidator.zipcodeUs()
FormValidator.postcodeUk()

// Mẫu regex
FormValidator.regex(r'^[A-Z]{3}\d{4}$', message: "Định dạng không hợp lệ")

// Validation tùy chỉnh
FormValidator.custom(
  message: "Giá trị không hợp lệ",
  validate: (data) => data != null,
)
```

<div id="chaining-validation-rules"></div>

## Chuỗi quy tắc Validation

Chuỗi nhiều quy tắc một cách mượt mà bằng phương thức instance. Mỗi phương thức trả về `FormValidator`, cho phép bạn xây dựng quy tắc:

``` dart
FormValidator validator = FormValidator()
    .notEmpty()
    .email()
    .maxLength(50);

FormValidationResult result = validator.check("user@example.com");

if (!result.isValid) {
  List<String> errors = result.errorMessages();
  print(errors);
}
```

Tất cả constructor được đặt tên đều có phương thức instance có thể chuỗi tương ứng:

``` dart
FormValidator()
    .notEmpty(message: "Bắt buộc")
    .email(message: "Email không hợp lệ")
    .minLength(5, message: "Quá ngắn")
    .maxLength(100, message: "Quá dài")
    .beginsWith("user", message: "Phải bắt đầu bằng 'user'")
    .lowercase(message: "Phải là chữ thường")
```

<div id="nullable-fields"></div>

## Trường Nullable

Sử dụng `.nullable()` để đánh dấu một validator là tùy chọn. Khi nullable, giá trị null hoặc rỗng tự động vượt qua validation — tất cả các quy tắc khác chỉ được áp dụng khi có giá trị.

``` dart
// Nickname là tùy chọn, nhưng nếu được cung cấp phải có 3–20 ký tự
Field.text(
  "Nickname",
  validator: FormValidator()
      .minLength(3)
      .maxLength(20)
      .nullable(),
)
```

Không có `.nullable()`, một trường rỗng sẽ không đạt quy tắc `minLength`. Với `.nullable()`, để trống trường đó sẽ vượt qua validation.

Điều này hữu ích cho các trường hồ sơ tùy chọn, thông tin liên hệ phụ, hoặc bất kỳ trường nào chỉ được validation khi người dùng điền vào. Xem [tài liệu Forms](/docs/7.x/forms) để biết cách `nullable()` tích hợp với các widget `Field`.

<div id="custom-validation"></div>

## Validation tùy chỉnh

### Quy tắc tùy chỉnh (Inline)

Sử dụng `.custom()` để thêm logic validation inline:

``` dart
FormValidator.custom(
  message: "Tên người dùng đã được sử dụng",
  validate: (data) {
    // Trả về true nếu hợp lệ, false nếu không hợp lệ
    return !_takenUsernames.contains(data);
  },
)
```

Hoặc chuỗi với các quy tắc khác:

``` dart
FormValidator()
    .notEmpty()
    .custom(
      message: "Phải bắt đầu bằng chữ cái",
      validate: (data) => RegExp(r'^[a-zA-Z]').hasMatch(data.toString()),
    )
```

### Validation bằng nhau

Kiểm tra xem giá trị có khớp với giá trị khác:

``` dart
FormValidator()
    .notEmpty()
    .equals(
      _passwordController.text,
      message: "Mật khẩu phải khớp",
    )
```

<div id="form-validator-with-fields"></div>

## Sử dụng FormValidator với Fields

`FormValidator` tích hợp với widget `Field` trong form. Truyền validator đến tham số `validator`:

``` dart
class RegisterForm extends NyFormData {
  RegisterForm({String? name}) : super(name ?? "register");

  @override
  fields() => [
        Field.text(
          "Name",
          autofocus: true,
          validator: FormValidator.notEmpty(),
        ),
        Field.email("Email", validator: FormValidator.email()),
        Field.password(
          "Password",
          validator: FormValidator.password(strength: 1),
        ),
      ];
}
```

Bạn cũng có thể sử dụng validator chuỗi với fields:

``` dart
Field.text(
  "Username",
  validator: FormValidator()
      .notEmpty(message: "Tên người dùng là bắt buộc")
      .minLength(3, message: "Ít nhất 3 ký tự")
      .maxLength(20, message: "Tối đa 20 ký tự"),
)

Field.slider(
  "Rating",
  validator: FormValidator.minValue(4, message: "Đánh giá phải ít nhất 4"),
)
```

<div id="validation-rules"></div>

## Các quy tắc Validation có sẵn

Tất cả quy tắc có sẵn cho `FormValidator`, dưới dạng constructor được đặt tên và phương thức có thể chuỗi:

| Quy tắc | Constructor được đặt tên | Phương thức có thể chuỗi | Mô tả |
|------|-------------------|------------------|-------------|
| Email | `FormValidator.email()` | `.email()` | Xác thực định dạng email |
| Password | `FormValidator.password(strength: 1)` | `.password(strength: 1)` | Độ mạnh 1: 8+ ký tự, 1 chữ hoa, 1 số. Độ mạnh 2: + 1 ký tự đặc biệt |
| Not Empty | `FormValidator.notEmpty()` | `.notEmpty()` | Không được rỗng |
| Min Length | `FormValidator.minLength(5)` | `.minLength(5)` | Độ dài chuỗi tối thiểu |
| Max Length | `FormValidator.maxLength(100)` | `.maxLength(100)` | Độ dài chuỗi tối đa |
| Min Value | `FormValidator.minValue(18)` | `.minValue(18)` | Giá trị số tối thiểu (cũng hoạt động trên độ dài chuỗi, danh sách, map) |
| Max Value | `FormValidator.maxValue(100)` | `.maxValue(100)` | Giá trị số tối đa |
| Min Size | `FormValidator.minSize(2)` | `.minSize(2)` | Kích thước tối thiểu cho danh sách/chuỗi |
| Max Size | `FormValidator.maxSize(5)` | `.maxSize(5)` | Kích thước tối đa cho danh sách/chuỗi |
| Contains | `FormValidator.contains(['a', 'b'])` | `.contains(['a', 'b'])` | Giá trị phải chứa một trong các giá trị cho |
| Begins With | `FormValidator.beginsWith("https://")` | `.beginsWith("https://")` | Chuỗi phải bắt đầu bằng tiền tố |
| Ends With | `FormValidator.endsWith(".com")` | `.endsWith(".com")` | Chuỗi phải kết thúc bằng hậu tố |
| URL | `FormValidator.url()` | `.url()` | Xác thực định dạng URL |
| Numeric | `FormValidator.numeric()` | `.numeric()` | Phải là số |
| Boolean True | `FormValidator.booleanTrue()` | `.booleanTrue()` | Phải là `true` |
| Boolean False | `FormValidator.booleanFalse()` | `.booleanFalse()` | Phải là `false` |
| Date | `FormValidator.date()` | `.date()` | Phải là ngày hợp lệ |
| Date In Past | `FormValidator.dateInPast()` | `.dateInPast()` | Ngày phải trong quá khứ |
| Date In Future | `FormValidator.dateInFuture()` | `.dateInFuture()` | Ngày phải trong tương lai |
| Age Is Older | `FormValidator.dateAgeIsOlder(18)` | `.dateAgeIsOlder(18)` | Tuổi phải lớn hơn N |
| Age Is Younger | `FormValidator.dateAgeIsYounger(65)` | `.dateAgeIsYounger(65)` | Tuổi phải nhỏ hơn N |
| Capitalized | `FormValidator.capitalized()` | `.capitalized()` | Chữ cái đầu phải viết hoa |
| Lowercase | `FormValidator.lowercase()` | `.lowercase()` | Tất cả ký tự phải viết thường |
| Uppercase | `FormValidator.uppercase()` | `.uppercase()` | Tất cả ký tự phải viết hoa |
| Phone US | `FormValidator.phoneNumberUs()` | `.phoneNumberUs()` | Định dạng số điện thoại US |
| Phone UK | `FormValidator.phoneNumberUk()` | `.phoneNumberUk()` | Định dạng số điện thoại UK |
| Zipcode US | `FormValidator.zipcodeUs()` | `.zipcodeUs()` | Định dạng zipcode US |
| Postcode UK | `FormValidator.postcodeUk()` | `.postcodeUk()` | Định dạng postcode UK |
| Regex | `FormValidator.regex(r'pattern')` | `.regex(r'pattern')` | Phải khớp mẫu regex |
| Equals | -- | `.equals(otherValue)` | Phải bằng giá trị khác |
| Custom | `FormValidator.custom(validate: fn)` | `.custom(validate: fn)` | Hàm validation tùy chỉnh |
| Nullable | — | `.nullable()` | Giá trị null hoặc rỗng tự động vượt qua; quy tắc chỉ áp dụng khi có giá trị |

Tất cả quy tắc chấp nhận tham số `message` tùy chọn để tùy chỉnh thông báo lỗi.

<div id="creating-custom-validation-rules"></div>

## Tạo quy tắc Validation tùy chỉnh

Để tạo quy tắc validation có thể tái sử dụng, kế thừa class `FormRule`:

``` dart
class FormRuleUsername extends FormRule {
  @override
  String? rule = "username";

  @override
  String? message = "The {{attribute}} must be a valid username.";

  FormRuleUsername({String? message}) {
    if (message != null) {
      this.message = message;
    }
  }

  @override
  bool validate(data) {
    if (data is! String) return false;
    // Username: chữ-số, gạch dưới, 3-20 ký tự
    return RegExp(r'^[a-zA-Z0-9_]{3,20}$').hasMatch(data);
  }
}
```

Sử dụng `{{attribute}}` làm placeholder trong `message` -- nó sẽ được thay thế bằng nhãn của trường khi chạy.

### Sử dụng FormRule tùy chỉnh

Thêm quy tắc tùy chỉnh vào `FormValidator` bằng `FormValidator.rule()`:

``` dart
FormValidator validator = FormValidator.rule([
  FormRuleNotEmpty(),
  FormRuleUsername(),
]);

FormValidationResult result = validator.check("my_username");
```

Hoặc sử dụng phương thức `.custom()` cho quy tắc một lần mà không cần tạo class:

``` dart
FormValidator()
    .notEmpty()
    .custom(
      message: "Tên người dùng phải bắt đầu bằng chữ cái",
      validate: (data) => RegExp(r'^[a-zA-Z]').hasMatch(data.toString()),
    )
```
