diff --git a/articles/building-apps/forms-data/add-form/index.adoc b/articles/building-apps/forms-data/add-form/index.adoc index 5ac9a05cee..e6d92fb94b 100644 --- a/articles/building-apps/forms-data/add-form/index.adoc +++ b/articles/building-apps/forms-data/add-form/index.adoc @@ -15,6 +15,6 @@ In business applications, forms play a central role in *displaying and collectin Vaadin offers a range of components and utilities to simplify form building. However, because forms and data binding cover a broad area, the topic is divided into several focused guides. Each one covers a specific aspect of form development. -The guides use Flow views. For forms in React views, see the <> reference guide. +The guides use Flow views. For forms in React views, see the <<{articles}/flow/react-views/forms#,Forms>> reference guide. section_outline::[] diff --git a/articles/building-apps/forms-data/consistency/domain-primitives.adoc b/articles/building-apps/forms-data/consistency/domain-primitives.adoc index 4e32ba1704..7326ead0b7 100644 --- a/articles/building-apps/forms-data/consistency/domain-primitives.adoc +++ b/articles/building-apps/forms-data/consistency/domain-primitives.adoc @@ -372,4 +372,4 @@ If you now use the `Quantity` domain primitive in a <<{articles}/flow/react-view Multi-value domain primitives are converted into their own TypeScript types, as long as they meet the requirements of the <<{articles}/flow/react-views/browser-callable-services/type-conversion#objects,type conversion>> for objects. -The input is validated on the server side by the domain primitive constructors, during JSON deserialization. However, an `IllegalArgumentException` thrown here becomes a `400 Bad Request` on the client side. The client is able to deduce that this is a validation error, but not from which field. Your system remains safe from bad data, but the user experience is not good. To improve it, you have to <<{articles}/hilla/guides/forms/binder-validation#defining-custom-client-side-validators,define custom client-side validators>> on your fields. +The input is validated on the server side by the domain primitive constructors, during JSON deserialization. However, an `IllegalArgumentException` thrown here becomes a `400 Bad Request` on the client side. The client is able to deduce that this is a validation error, but not from which field. Your system remains safe from bad data, but the user experience is not good. To improve it, add <<{articles}/flow/react-views/forms/validation#field-validators,validators>> to the fields in the view. diff --git a/articles/building-apps/forms-data/consistency/validation.adoc b/articles/building-apps/forms-data/consistency/validation.adoc index 108d4ee9cf..60338361bf 100644 --- a/articles/building-apps/forms-data/consistency/validation.adoc +++ b/articles/building-apps/forms-data/consistency/validation.adoc @@ -98,7 +98,7 @@ public class UserService { } ---- -Bean Validation annotations have the added benefit of being supported by both Flow views and React views, for use in user interface validation. For more information about this, see the relevant <<{articles}/flow/binding-data/components-binder-beans#using-jsr-303-bean-validation,Flow>> and <<{articles}/hilla/guides/forms/binder-validation#,React>> documentation pages. +Bean Validation annotations have the added benefit of being supported by both Flow views and React views, for use in user interface validation. For more information about this, see the relevant <<{articles}/flow/binding-data/components-binder-beans#using-jsr-303-bean-validation,Flow>> and <<{articles}/flow/react-views/forms/validation#,React>> documentation pages. For more information about Jakarta Bean Validation, visit the https://beanvalidation.org/[Bean Validation website]. For more information about using Bean Validation in Spring, see the https://docs.spring.io/spring-framework/reference/core/validation/beanvalidation.html[Spring documentation]. diff --git a/articles/building-apps/react/add-view.adoc b/articles/building-apps/react/add-view.adoc index 8a48f90231..5e4a3660e8 100644 --- a/articles/building-apps/react/add-view.adoc +++ b/articles/building-apps/react/add-view.adoc @@ -137,7 +137,7 @@ Avoid explicit routes unless necessary. Letting the routing system resolve route == Building Forms in a View -To collect and edit data in a React view, combine Vaadin input components with the `useForm` hook, which binds fields to a Java data type and applies the same validation rules on the client. Forms are an in-depth topic of their own; see the <<{articles}/hilla/guides/forms#,Forms reference guide>> for field binding, validation, and loading and saving data. +To collect and edit data in a React view, combine Vaadin input components with the `useForm` hook, which binds fields to a Java data type and applies the same validation rules on the client. Forms are an in-depth topic of their own. See the <<{articles}/flow/react-views/forms#,Forms reference guide>> for field binding, validation, and loading and saving data. == Next Steps diff --git a/articles/components/_input-field-common-features.adoc b/articles/components/_input-field-common-features.adoc index d1da55ee39..3b6d2fd4c2 100644 --- a/articles/components/_input-field-common-features.adoc +++ b/articles/components/_input-field-common-features.adoc @@ -228,7 +228,7 @@ A separate single-character, regular expression can be used to restrict the char // tag::binder[] .Data Binding & Custom Validation [NOTE] -Flow and Hilla offer an advanced API called Binder that allows you to bind data and add custom validation rules for multiple fields, creating forms. You can learn more about Binder from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/hilla/guides/forms/binder-validation#,Hilla>> articles. +Flow and React views can bind fields to the properties of a data object and validate them, creating forms. You can learn more from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/flow/react-views/forms/validation#,React>> articles. // end::binder[] diff --git a/articles/components/auto-form/index.adoc b/articles/components/auto-form/index.adoc index 9f010b55f1..45569b59f7 100644 --- a/articles/components/auto-form/index.adoc +++ b/articles/components/auto-form/index.adoc @@ -236,7 +236,7 @@ Note that `onSubmitError` callback is not be called for form validation errors, == Validation -Auto Form uses the same validation mechanism as <>. You can add Bean validation annotations to your entity classes, and the form automatically validates the input values on both client and server. +Auto Form uses the same validation mechanism as <<{articles}/flow/react-views/forms/validation#,forms in React views>>. You can add Bean validation annotations to your entity classes, and the form automatically validates the input values on both client and server. In the following example, annotations are added to the `Employee` class to ensure that `firstName` is neither `null` nor an empty string: @@ -255,7 +255,7 @@ public class Employee { } ---- -For further information on using validation, refer to the <> documentation. +For further information on using validation, refer to the <<{articles}/flow/react-views/forms/validation#,Form Validation>> documentation. One notable difference to default forms is adding custom client-side validators. With Auto Form, custom client-side validators are added using the `validators` property in the `fieldOptions`. The following example shows how to add a custom validator to the `firstName` field: diff --git a/articles/components/date-picker/index.adoc b/articles/components/date-picker/index.adoc index b00564ea6e..fd0f8a119e 100644 --- a/articles/components/date-picker/index.adoc +++ b/articles/components/date-picker/index.adoc @@ -138,7 +138,7 @@ include::{root}/frontend/demo/component/datepicker/react/date-picker-custom-vali endif::[] -- -Binder can also be used to organize data binding and validation for multiple fields, creating forms. You can learn more about Binder from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/hilla/guides/forms/binder-validation#,Hilla>> articles. +Binder can also be used to organize data binding and validation for multiple fields, creating forms. You can learn more about forms from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/flow/react-views/forms/validation#,React>> articles. [[configuring-individual-dates]] diff --git a/articles/components/date-time-picker/index.adoc b/articles/components/date-time-picker/index.adoc index 37f546a977..a8a6c86254 100644 --- a/articles/components/date-time-picker/index.adoc +++ b/articles/components/date-time-picker/index.adoc @@ -223,7 +223,7 @@ include::{root}/frontend/demo/component/datetimepicker/react/date-time-picker-cu endif::[] -- -Binder can also be used to organize data binding and validation for multiple fields, creating forms. You can learn more about Binder from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/hilla/guides/forms/binder-validation#,Hilla>> articles. +Binder can also be used to organize data binding and validation for multiple fields, creating forms. You can learn more about forms from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/flow/react-views/forms/validation#,React>> articles. == Week Numbers diff --git a/articles/components/time-picker/index.adoc b/articles/components/time-picker/index.adoc index 698050749a..8d77e4dd96 100644 --- a/articles/components/time-picker/index.adoc +++ b/articles/components/time-picker/index.adoc @@ -229,7 +229,7 @@ include::{root}/frontend/demo/component/timepicker/react/time-picker-custom-vali endif::[] -- -Binder can also be used to organize data binding and validation for multiple fields, creating forms. You can learn more about Binder from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/hilla/guides/forms/binder-validation#,Hilla>> articles. +Binder can also be used to organize data binding and validation for multiple fields, creating forms. You can learn more about forms from the corresponding <<{articles}/flow/binding-data/components-binder-validation#,Flow>> and <<{articles}/flow/react-views/forms/validation#,React>> articles. // Basic Features diff --git a/articles/flow/react-views/browser-callable-services/code-generation.adoc b/articles/flow/react-views/browser-callable-services/code-generation.adoc index 3ee4508fb3..05b89c93b9 100644 --- a/articles/flow/react-views/browser-callable-services/code-generation.adoc +++ b/articles/flow/react-views/browser-callable-services/code-generation.adoc @@ -52,7 +52,7 @@ The client that the service modules use, unless the project has a client of its A TypeScript type for each Java type that the services use, in directories named after the Java package. A nested class has a directory named after the enclosing class, such as [filename]`Sort/Direction.ts`. See <>. [filename]`{package}/{type}Model.ts`:: -A model of each type, for binding forms to it. See <<{articles}/hilla/guides/forms/binder#,Binding Data to Forms>>. +A model of each type, for binding forms to it. See <<../forms#,Forms>>. [filename]`{package}/{type}Union.ts`:: A union of the subtypes of a polymorphic type. See <>. diff --git a/articles/flow/react-views/browser-callable-services/index.adoc b/articles/flow/react-views/browser-callable-services/index.adoc index 8df68fd21b..172b917e21 100644 --- a/articles/flow/react-views/browser-callable-services/index.adoc +++ b/articles/flow/react-views/browser-callable-services/index.adoc @@ -236,7 +236,7 @@ include::{root}/frontend/demo/fusion/errorhandling/catch-invalid-args.ts[] A method can also fail a call with an `EndpointValidationError` by throwing an [classname]`EndpointValidationException`. Vaadin also checks the constraints on the return value. When the return value fails them, the call fails as described in <<#errors,Errors>>. -When a form bound with the `useForm` hook submits its value to a service method, the form shows the validation errors of the call in its fields. See <<{articles}/hilla/guides/forms/binder-validation#,Form Validation>>. +When a form bound with the `useForm` hook submits its value to a service method, the form shows the validation errors of the call in its fields. See <<../forms#server-validation,Server-Side Validation>>. [[errors]] diff --git a/articles/flow/react-views/component-events.adoc b/articles/flow/react-views/component-events.adoc index 0580ecdbd8..077d128ab7 100644 --- a/articles/flow/react-views/component-events.adoc +++ b/articles/flow/react-views/component-events.adoc @@ -30,4 +30,4 @@ include::{root}/frontend/demo/fusion/application/events/value-changed-view.tsx[r React passes the custom events of Vaadin components, such as `value-changed`, to the listener as they are. For standard browser events, such as `click`, React passes its own synthetic event instead. The browser event is then in `event.nativeEvent`. -To keep a form's state in sync with its fields without handling their change events, bind the fields with the `useForm` hook. See <<{articles}/hilla/guides/forms/binder#,Binding Data to Forms>>. +To keep a form's state in sync with its fields without handling their change events, bind the fields with the `useForm` hook. See <>. diff --git a/articles/flow/react-views/forms/api.adoc b/articles/flow/react-views/forms/api.adoc new file mode 100644 index 0000000000..1fe72c76a1 --- /dev/null +++ b/articles/flow/react-views/forms/api.adoc @@ -0,0 +1,109 @@ +--- +title: Form API +page-title: Hooks and types of form binding in React views | Vaadin +description: The hooks that bind forms in React views, their results, and the types for validation. +meta-description: API reference for Vaadin form binding in React views - useForm, useFormPart, useFormArrayPart, the field function, validators, errors, and form models. +order: 50 +--- + + += Form API +:toclevels: 2 + +The hooks of form binding are in the `@vaadin/hilla-react-form` package. The validation types are in `@vaadin/hilla-lit-form`, and the model helpers in `@vaadin/hilla-models`. + +section_outline::[] + + +[[use-form]] +== useForm + +`useForm(model, config?)` creates a form for the given model, and returns its state and functions. The optional configuration object has these properties: + +`onSubmit(value)`:: +An asynchronous function that [methodname]`submit()` calls with the value of the form. See <>. + +`onChange(oldValue)`:: +A function that the form calls when it updates its value, with the value before the update. + +The result has the state properties described in <>, and these functions: + +`field(model)`:: +Returns the properties that bind a component to the field of the given model. Spread them on the component. See <>. + +`read(value)`:: +Sets the value and the default value of the form, and clears the validation errors. With `null` or `undefined`, it clears the form. + +`reset()`:: +Sets the value back to the default value. + +`clear()`:: +Sets the value and the default value to the default values of the model. + +`submit()`:: +Validates the form, and calls `onSubmit`. Returns a promise that resolves with the result of `onSubmit`, or rejects with a [classname]`ValidationError`. + +`setValue(value)`:: +Sets the value of the form. + +`setDefaultValue(value)`:: +Sets the default value of the form, which affects the `dirty` state and [methodname]`reset()`. + +`validate()`:: +Validates the form, and returns a promise of its errors. + +`addValidator(validator)`:: +Adds a validator to the form. See <>. + +`setValidators(validators)`:: +Replaces the validators of the form. + +`setVisited(visited)`:: +Sets the `visited` state. + +`update()`:: +Re-renders the component. + + +[[use-form-part]] +== useFormPart + +`useFormPart(model)` returns the state and functions of a part of a form, such as a field or a nested object. The model must belong to a form that a [methodname]`useForm()` call in the same or a parent component created. + +The result has the state properties described in <>, except `submitting`, and the `field()`, `setValue()`, `validate()`, `addValidator()`, `setValidators()`, and `setVisited()` functions of [methodname]`useForm()`. They apply to the part. + +The validators of a field include the validators of its Bean Validation constraints. Calling [methodname]`setValidators()` replaces them, too. + + +[[use-form-array-part]] +== useFormArrayPart + +`useFormArrayPart(model)` returns the state and functions of a list or an array in a form, like [methodname]`useFormPart()`, but without `field()`. Its `items` property contains a model for each item. The model must be non-nullable. See <>. + + +[[validation-types]] +== Validation Types + +The `@vaadin/hilla-lit-form` package has these types for validation: + +[classname]`Validator`:: +An object with a `message`, a [methodname]`validate(value)` function, and an optional `impliesRequired` flag. See <>. + +[classname]`ValueError`:: +A validation error, with the `property` that identifies the field, the `message`, the `value`, and the `validator` that failed. + +[classname]`ValidationError`:: +The error that [methodname]`submit()` rejects with when the validation fails. Its `errors` property contains the [classname]`ValueError` objects. + +[classname]`BinderRoot`:: +Its static `interpolateMessageCallback` property replaces the placeholders in the messages of constraints. See <>. + +The package also has a validator class for each Bean Validation constraint that the browser checks, such as [classname]`NotBlank`, [classname]`Size`, and [classname]`Pattern`. They take an options object with a `message` and the attributes of the constraint, such as `new Size({ min: 3, max: 20 })`. + + +[[models]] +== Form Models + +A generated form model, such as `PersonModel`, is both a value and a type with the same name. Pass the value to the hooks, and use the type for the properties of components that get a part of the form. + +The `$defaultValue` symbol from `@vaadin/hilla-models` gives the default value of a model, such as `PersonModel[$defaultValue]`. diff --git a/articles/flow/react-views/forms/field-components.adoc b/articles/flow/react-views/forms/field-components.adoc new file mode 100644 index 0000000000..c30e2bf5c1 --- /dev/null +++ b/articles/flow/react-views/forms/field-components.adoc @@ -0,0 +1,66 @@ +--- +title: Field Components +page-title: Field components that bind to forms in React views | Vaadin +description: The Vaadin components that the field() function binds, and the Java types of the properties that they edit. +meta-description: Reference for Vaadin forms in React views - which Vaadin field components the field() function binds, and the Java property types that each of them edits. +order: 40 +--- + + += Field Components + +The [methodname]`field()` function binds the following Vaadin components. Each component edits a property of one of the listed Java types: + +[cols="2,3"] +|=== +|Component |Java type of the property + +|<<{articles}/components/text-field#,Text Field>>, <<{articles}/components/text-area#,Text Area>>, <<{articles}/components/password-field#,Password Field>>, <<{articles}/components/email-field#,Email Field>> +|`String` + +|<<{articles}/components/number-field#,Integer Field, Number Field>> +|A number type, such as `int`, `Integer`, `double`, or `BigDecimal` + +|<<{articles}/components/checkbox#,Checkbox>> +|`boolean` + +|<<{articles}/components/date-picker#,Date Picker>> +|`LocalDate` + +|<<{articles}/components/time-picker#,Time Picker>> +|`LocalTime` + +|<<{articles}/components/date-time-picker#,Date Time Picker>> +|`LocalDateTime` + +|<<{articles}/components/select#,Select>>, <<{articles}/components/radio-button#,Radio Button Group>> +|`String` or an enum, matched to the values of the items + +|<<{articles}/components/combo-box#,Combo Box>> +|`String` or an enum, matched to the values of the items, or an object, which is the selected item + +|<<{articles}/components/checkbox#,Checkbox Group>> +|A list or a set of `String` + +|<<{articles}/components/multi-select-combo-box#,Multi-Select Combo Box>> +|A list or a set of the items + +|<<{articles}/components/custom-field#,Custom Field>> +|`String`. By default, the value combines the values of the fields inside the custom field, separated by tab characters. + +|<<{articles}/components/rich-text-editor#,Rich Text Editor>> +|`String`, in the Delta format of the editor +|=== + +For example, a combo box can edit a `Country` property of a form, when its items are `Country` objects: + +[source,tsx] +---- + +---- + +See <> for the whole example. + +When the user empties a number field, the value of a nullable number becomes `undefined`. A non-nullable number becomes `NaN`, and the field shows the error "must be a number". + +The [methodname]`field()` function throws an error for a component that isn't a field, such as a <<{articles}/components/list-box#,List Box>>. It doesn't read the state of a <<{articles}/components/switch#,Switch>>, so use a checkbox for a `boolean` property instead. diff --git a/articles/flow/react-views/forms/form-state.adoc b/articles/flow/react-views/forms/form-state.adoc new file mode 100644 index 0000000000..2a360553f8 --- /dev/null +++ b/articles/flow/react-views/forms/form-state.adoc @@ -0,0 +1,96 @@ +--- +title: Form State +page-title: The state of forms and fields in React views | Vaadin +description: The value, the dirty and invalid states, and the errors of a form and its fields, and how to change the value of a field from code. +meta-description: Reference for the state of Vaadin forms in React views - value, default value, dirty, visited, invalid, errors, and submitting - and changing values from code. +order: 20 +--- + + += Form State +:toclevels: 2 + +The result of [methodname]`useForm()` describes the state of the form. The result of [methodname]`useFormPart()` describes the state of a part of the form, such as a single field. The component re-renders when the state changes. + +section_outline::[] + + +[[properties]] +== State Properties + +The form and its parts have these properties: + +`value`:: +The current value. + +`defaultValue`:: +The value that was last read or submitted. + +`dirty`:: +`true` when the value differs from the default value. A form stays dirty when the user changes a field back to its original value, because the form then has a new value object. A single field with a string, number, or boolean value compares the values themselves. + +`visited`:: +`true` when the field has lost focus at least once. It's only set for fields, not for the form or for parts that contain several fields. + +`required`:: +`true` when the field is required, as described in <>. + +`invalid`:: +`true` when there are validation errors. + +`errors`:: +The validation errors of the form or the part, including the errors of the fields that it contains. Each error has a `property`, which identifies the field by its model or its path, and a `message`. When a component reports its own input as invalid, for example because a required field is empty, the errors also contain an error with an empty message. + +`ownErrors`:: +The validation errors that belong to the form or the part itself, and not to a field that it contains. + +The form also has a `submitting` property, which is `true` while the `onSubmit` callback runs. See <>. + +For example, the following button is enabled only when there is something to save, and the list shows all the errors of the form: + +[source,tsx] +---- +const { model, field, dirty, submitting, errors } = useForm(PersonModel, { + onSubmit: async (person) => { + await PersonService.savePerson(person); + }, +}); + +// ... + + +
    + {errors + .filter((error) => error.message) + .map((error, index) => ( +
  • {error.message}
  • + ))} +
+---- + + +[[changing-values]] +== Changing Values from Code + +To change the value of a field from code, call [methodname]`setValue()` of its part, or of the form with a new value for the whole form. Don't change the objects in `value` directly: the form doesn't notice such changes, so it neither re-renders nor validates. + +The following example has a combo box for a country and another for a city. When the country changes, the view loads the cities of the country, and selects the first city if the current one isn't among them: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/formchange/CompanyOfficeView.tsx[tags=snippet,indent=0] +---- + +The form, the classes, and the service of the example are as follows: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/formchange/CompanyOffice.java[] +---- + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/formchange/OfficeService.java[] +---- diff --git a/articles/flow/react-views/forms/index.adoc b/articles/flow/react-views/forms/index.adoc new file mode 100644 index 0000000000..fac37e29be --- /dev/null +++ b/articles/flow/react-views/forms/index.adoc @@ -0,0 +1,93 @@ +--- +title: Forms +page-title: Bind form fields to Java types in React views | Vaadin +description: Bind the fields of a React view to a Java type, validate them with Bean Validation, and save the value with a browser-callable service. +meta-description: Reference for Vaadin form binding in React views - the useForm hook, generated form models, binding fields, loading values, submitting, and server-side errors. +order: 50 +--- + + += Forms +:toclevels: 2 + +The [methodname]`useForm()` hook from `@vaadin/hilla-react-form` binds the fields of a React view to a Java type. It keeps the value of the form, validates it with the Bean Validation constraints of the Java type, and submits it to a <<../browser-callable-services#,browser-callable service>>. + +section_outline::[] + + +[[models]] +== Form Models + +For each Java type that a browser-callable service uses, Vaadin generates a form model next to its TypeScript type. For example, the model of `Person` is in [filename]`PersonModel.ts`, beside [filename]`Person.ts`. See <<../browser-callable-services/code-generation#generated-files,Generated Files>>. + +A model describes the properties of the type and their constraints. It doesn't contain a value. The examples on these pages use this record and service: + +.[filename]`Person.java` +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/person/Person.java[tags=snippet,indent=0] +---- + +.[filename]`PersonService.java` +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/person/PersonService.java[tags=snippet,indent=0] +---- + +The package of the records is annotated with [annotationname]`@NullMarked`, so their properties are non-nullable, except `birthDate`. See <<../browser-callable-services/type-nullability#,Type Nullability>>. + + +[[binding-fields]] +== Binding Fields + +Pass the generated model to [methodname]`useForm()`. Then spread the result of [methodname]`field()` on each field component, with the model of its property as the argument: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/person/person-form.tsx[tags=snippet,indent=0] +---- + +The [methodname]`field()` function connects the component to the form. It sets the `name`, the value, the `required` state, the `invalid` state, and the error message of the component. It updates the value of the form when the user changes the value of the component. The components that it supports are listed in <>. + +The `model` of the form has a model for each property of the type, such as `model.firstName`. A property that is an object has models for its own properties, such as `model.address.street`. See <>. + + +[[loading]] +== Loading a Value + +The [methodname]`read()` function sets the value of the form, for example the value that a service returns. It also makes the value the default value of the form, and clears the validation errors. Passing `null` or `undefined` clears the form. + +The form has a value before [methodname]`read()` is called: each property has the default value of its model, such as an empty string for a `String` property. + +Two more functions change the whole value: + +[methodname]`reset()`:: +Sets the value back to the default value, which is the value last read or submitted. + +[methodname]`clear()`:: +Sets the value, and the default value, to the default values of the model. + + +[[submitting]] +== Submitting + +To save the value, pass an `onSubmit` callback to [methodname]`useForm()`, and call [methodname]`submit()`, for example when the user clicks a button. The [methodname]`submit()` function first validates the whole form. When the form is valid, it calls the callback with the value of the form, and resolves with what the callback returns. While the promise of the callback is pending, the `submitting` property of the form is `true`. + +When the validation fails, [methodname]`submit()` doesn't call the callback. It rejects with a [classname]`ValidationError` from `@vaadin/hilla-lit-form`, and the fields show the errors. Catch the error, so that it doesn't become an unhandled rejection: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/person/person-form.tsx[tags=save,indent=0] +---- + +Other errors of the callback, such as an `EndpointError`, reject [methodname]`submit()` as they are. + +After the form calls the callback, it uses the submitted value as its default value. This happens also when the callback fails, for example when the server rejects the value. The form is then no longer dirty, and [methodname]`reset()` returns to the submitted value. A submit that fails the validation in the browser doesn't change the default value. + + +[[server-validation]] +== Server-Side Validation + +The service validates its arguments again, as described in <<../browser-callable-services#validation,Parameter Validation>>. When an object from the form fails the validation, the call rejects with an `EndpointValidationError`. Then [methodname]`submit()` shows each error in the field whose path matches the property of the error, such as `address.street` or `phoneNumbers.0.number`, and rejects with a [classname]`ValidationError`. + +An error that matches no field is only in the `errors` property of the [classname]`ValidationError`, and the form stays valid. The errors of constraints on the method parameters themselves, rather than on the properties of the object, don't match any field. A service can also report errors of its own, as described in <>. diff --git a/articles/flow/react-views/forms/nested-objects-arrays.adoc b/articles/flow/react-views/forms/nested-objects-arrays.adoc new file mode 100644 index 0000000000..4401ed7171 --- /dev/null +++ b/articles/flow/react-views/forms/nested-objects-arrays.adoc @@ -0,0 +1,60 @@ +--- +title: Nested Objects & Arrays +page-title: Nested objects and arrays in React view forms | Vaadin +description: Bind the properties of nested objects, split a form into components, and add and remove the items of a list. +meta-description: Reference for Vaadin forms in React views - binding nested objects, splitting forms into components with useFormPart, and lists with useFormArrayPart. +order: 30 +--- + + += Nested Objects & Arrays +:toclevels: 2 + +A property of a Java type can be another object, or a list of objects. The examples on this page use the `Person` record on <>, which has an address and a list of phone numbers: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/person/Address.java[tags=snippet,indent=0] +---- + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/person/PhoneNumber.java[tags=snippet,indent=0] +---- + +Keep nested objects and lists non-nullable, for example by annotating their package with [annotationname]`@NullMarked`. The [methodname]`useFormArrayPart()` hook doesn't accept the model of a nullable list. + +section_outline::[] + + +[[nested-objects]] +== Nested Objects + +The model of an object property has models for the properties of the object, so a field can bind to them directly, such as `field(model.address.street)`. The name of the field is the path of the property, `address.street`. The errors of the server use the same path, so that they appear in the right field. + +To split a form into components, pass the model of a nested object to a component, and call [methodname]`useFormPart()` there. The part has a [methodname]`field()` function of its own, and the state of the nested object, as described in <>. Type the property of the component with the generated model type, which has the same name as the model: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/person/person-details-form.tsx[tags=address,indent=0] +---- + + +[[arrays]] +== Arrays + +For a list or an array, call [methodname]`useFormArrayPart()` with its model. Besides the state of the list, the result has an `items` property, with a model for each item. Bind the fields of each item to its model, for example in a component of their own: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/person/person-details-form.tsx[tags=phone-number,indent=0] +---- + +To add or remove items, call [methodname]`setValue()` with a new list. The default value of a model, such as `PhoneNumberModel[$defaultValue]`, is an empty item. Import `$defaultValue` from `@vaadin/hilla-models`: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/person/person-details-form.tsx[tags=snippet,indent=0] +---- + +The models in `items` follow the positions in the list, so use the index as the key. The name of a field in an item contains the index, such as `phoneNumbers.0.number`. diff --git a/articles/flow/react-views/forms/validation.adoc b/articles/flow/react-views/forms/validation.adoc new file mode 100644 index 0000000000..da554b02c7 --- /dev/null +++ b/articles/flow/react-views/forms/validation.adoc @@ -0,0 +1,145 @@ +--- +title: Validation +page-title: Validate forms in React views with Bean Validation | Vaadin +description: The Bean Validation constraints that forms check in the browser, validators of your own, and errors that a service reports. +meta-description: Reference for Vaadin form validation in React views - Bean Validation in the browser, required fields, error messages, custom validators, and server errors. +order: 10 +--- + + += Form Validation +:toclevels: 2 + +A form validates its value in the browser, so that the user sees errors right away. The service validates the value again on the server, when the form is submitted. The browser runs the Bean Validation constraints of the Java type, and validators that the view adds. The server runs only the Bean Validation constraints, and checks of its own. + +The browser validates a field when the user changes its value, and when the field loses focus. On submit, it validates the whole form. + +section_outline::[] + + +[[constraints]] +== Bean Validation Constraints + +The generator copies these Jakarta Bean Validation constraints from the Java type to the form model, so that the browser checks them, too: + +* `@AssertFalse`, `@AssertTrue` +* `@DecimalMax`, `@DecimalMin`, `@Digits`, `@Max`, `@Min` +* `@Negative`, `@NegativeOrZero`, `@Positive`, `@PositiveOrZero` +* `@Email`, `@NotBlank`, `@NotEmpty`, `@Pattern`, `@Size` +* `@Future`, `@Past` +* `@NotNull`, `@Null` + +Only the server checks other constraints, such as `@PastOrPresent`, `@FutureOrPresent`, and constraints of your own. Their errors appear in the fields after a submit, as described in <>. + +For example, the `Person` record on <> has these constraints: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/person/Person.java[tags=snippet,indent=0] +---- + +The [annotationname]`@Valid` annotations make the server validate the nested objects, too. The browser validates them in any case. + + +[[required]] +== Required Fields + +A field is required when its property has a [annotationname]`@NotNull`, [annotationname]`@NotEmpty`, or [annotationname]`@NotBlank` constraint, or a [annotationname]`@Size` constraint with a `min` greater than 0. The [methodname]`field()` function sets the `required` state of the component, which shows an indicator. + +When a field isn't required and its value is empty, the browser doesn't run its other validators. For example, an empty postal code passes a [annotationname]`@Pattern` constraint. A number field is an exception: an empty field for a non-nullable number fails with the message "must be a number". + + +[[messages]] +== Error Messages + +When a constraint has no `message` attribute in Java, the browser shows a message of its own in English, such as "must not be blank". After a submit, the field shows the message of the server, which comes from Bean Validation. When the constraint has a `message` attribute, both use it. + +The browser shows the message as it's written in Java. It doesn't replace placeholders such as `{min}`. To replace them, set the static `interpolateMessageCallback` property of [classname]`BinderRoot`, from `@vaadin/hilla-lit-form`. It gets the message and the validator, which has the attributes of the constraint as properties: + +[source,typescript] +---- +import { BinderRoot } from '@vaadin/hilla-lit-form'; + +BinderRoot.interpolateMessageCallback = (message, validator) => + message.replace(/\{(\w+)\}/gu, (placeholder, name: string) => + String((validator as unknown as Record)[name] ?? placeholder), + ); +---- + + +[[validators]] +== Validators in the View + +A view can add validators of its own, for checks that have no Bean Validation constraint. Only the browser runs these validators, so they don't protect the data. When the check matters, make the service check it, too. + +A validator is an object with a `message` and a [methodname]`validate()` function. The function gets the value, and returns whether it's valid. It can also return a promise. Add the validators once, in an effect without dependencies. + +The examples in this section use this record: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/signup/SignUp.java[tags=snippet,indent=0] +---- + + +[[field-validators]] +=== Field Validators + +To validate a single field, get its part of the form with [methodname]`useFormPart()`, and call [methodname]`addValidator()`: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/signup/sign-up-form.tsx[tags=field,indent=0] +---- + +The field shows the `message` of the validator when [methodname]`validate()` returns `false`. + +The validators of the Bean Validation constraints are also available as classes in `@vaadin/hilla-lit-form`, with the same names. For example, `username.addValidator(new Size({ max: 20, message: 'At most 20 characters' }))` adds a [annotationname]`@Size` check to a field in the browser. + + +[[form-validators]] +=== Form Validators + +A check that involves several fields belongs to the form. Call [methodname]`addValidator()` of the form, and return the fields to mark from [methodname]`validate()`: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/signup/sign-up-form.tsx[tags=form,indent=0] +---- + +The [methodname]`validate()` function of a form validator gets the whole value of the form. It can return these values: + +`true` or an empty array:: +The value is valid. + +An object or an array of objects with a `property`, and an optional `message`:: +Each object marks the field of its `property` as invalid. The `property` is the model of the field, or its path, such as `'address.street'`. The `message` of the object replaces the message of the validator. + +`false`:: +The form is invalid, but no field shows the error. It's in the `errors` of the form. + +The browser runs the form validators together with the validators of the fields. + + +[[implies-required]] +=== Required Validators + +A validator with `impliesRequired: true` makes its field required, like a [annotationname]`@NotBlank` constraint. The browser then runs the validator also when the field is empty. + + +[[server-errors]] +== Errors from the Service + +A service can report validation errors of its own, for example for checks that need data from a database. Throw an [classname]`EndpointValidationException`, with a [classname]`ValidationErrorData` for each error. Its second argument is the path of the field that shows the error: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/fusion/forms/signup/SignUpService.java[tags=snippet,indent=0] +---- + +The form shows the error in the field, like the errors of the Bean Validation constraints: + +[source,tsx] +---- +include::{root}/frontend/demo/fusion/forms/signup/sign-up-form.tsx[tags=snippet,indent=0] +---- diff --git a/articles/hilla/_shared/guides/forms/_binder-load.adoc b/articles/hilla/_shared/guides/forms/_binder-load.adoc deleted file mode 100644 index ae75076bfe..0000000000 --- a/articles/hilla/_shared/guides/forms/_binder-load.adoc +++ /dev/null @@ -1,112 +0,0 @@ -// tag::content[] - -After your bindings are set, you'll need to fill the bound UI components with data from your business objects. - - -== Loading Data to Binder - -ifdef::hilla-react[] -You can use the [methodname]`read()` method from the [classname]`UseFormResult` instance to read values from a business object instance into the UI components. - -[source,tsx] ----- -import { useEffect } from 'react'; -import { useForm } from '@vaadin/hilla-react-form'; - -import { PersonService } from 'Frontend/generated/endpoints'; -import PersonModel from 'Frontend/generated/com/example/application/PersonModel'; - -export default function PersonView() { - const { read } = useForm(PersonModel); - - useEffect(() => { - PersonService.loadPerson().then(read); - }, []) - - // ... -} ----- - -Using the [methodname]`reset()` method will reset to the previous value, which is initially empty. - -[source,tsx] ----- -import { useForm } from '@vaadin/hilla-react-form'; - -import PersonModel from 'Frontend/generated/com/example/application/PersonModel'; - -export default function PersonView() { - const { reset } = useForm(PersonModel); - - return ( -
- // other form fields ... - -
- ); - -} ----- - -You can use the [methodname]`clear()` method to set the form to empty. - -[source,tsx] ----- -import { useForm } from '@vaadin/hilla-react-form'; - -import PersonModel from 'Frontend/generated/com/example/application/PersonModel'; - -export default function PersonView() { - const { clear } = useForm(PersonModel); - - return ( -
- // other form fields ... - -
- ); - -} ----- -endif::hilla-react[] - - -== Saving Data - -ifdef::hilla-react[] -You can define a [methodname]`submit` callback when calling [methodname]`useForm` to configure the [methodname]`onSubmit` behavior of the binder. - -Configuring a [methodname]`submit` behavior this way for the binder can be beneficial: - -- Binder can track the submission progress, which is useful for disabling a save button, for instance, when a submission is ongoing. -- Submission failures are applied to the form fields, automatically. Therefore. there is no need to do an explicit `try-catch`. -- Submission overall errors can be handled globally using a promise's `.catch` block. - -Below is an example in which [methodname]`submit` behavior is set to submit to a service method: - -[source,tsx] ----- -import { useForm } from '@vaadin/hilla-react-form'; - -import { PersonService } from 'Frontend/generated/endpoints'; -import PersonModel from 'Frontend/generated/com/example/application/PersonModel'; - -export default function PersonView() { - const { model, submit, field } = useForm(PersonModel, { - onSubmit: async (person) => { - await PersonService.savePerson(person); - } - }); - - return ( -
- - -
- ); - -} ----- -endif::hilla-react[] - -// end::content[] diff --git a/articles/hilla/_shared/guides/forms/_binder-validation.adoc b/articles/hilla/_shared/guides/forms/_binder-validation.adoc deleted file mode 100644 index 8d24b9fbb4..0000000000 --- a/articles/hilla/_shared/guides/forms/_binder-validation.adoc +++ /dev/null @@ -1,366 +0,0 @@ -// tag::content[] - -Hilla helps you validate user input based on the backend Java data model. It reads the Bean Validation (https://beanvalidation.org/2.0-jsr380/spec[`JSR-380`]) annotations on your Java data types and applies these constraints to the user input. Validation is enabled by default in all forms created with the Hilla `Binder` API. - -When creating forms in TypeScript with {techstack} and the `Binder` API, all data model constraints are automatically applied to your form fields. The `Binder` API validates most standard constraints, such as [annotationname]`@Max`, [annotationname]`@Size`, [annotationname]`@Pattern`, [annotationname]`@Email` on the client side, without a network round-trip delay (see the full list in the <> section later). When you eventually submit data to server-side services, Hilla validates all constraints on the server as well. The `Binder` API updates the form to show any server-side validation errors. - - -== Specifying Constraints - -Constraints are specified as a part of the data model, in the Java code. You can use any of the https://beanvalidation.org/2.0/spec/#builtinconstraints[built-in constraints] in addition to your own. - -This example defines constraints on individual object properties using Bean Validation annotations: - -[source,java] ----- -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; - -public class Employee { - @NotBlank - private String username; - - private String title; - - @Email(message = "Please enter a valid e-mail address") - private String email; - - // + other fields, constructors, setters and getters -} ----- - -During the build, when Hilla generates TypeScript types, it includes the constraint information in the generated model types. All forms that work with the same entity type have the same set of constraints. - -When you create a form for an entity type, it gets the user input validation automatically. - -ifdef::hilla-react[] -.Binding form fields to the data model -[source,tsx] ----- -import { AbstractModel } from '@vaadin/hilla-lit-form'; -import { FieldDirectiveResult, useForm } from '@vaadin/hilla-react-form'; - -import EmployeeModel from 'Frontend/generated/com/example/application/EmployeeModel'; -// other imports - -export default function EmployeeView() { - const { model, field } = useForm(EmployeeModel); - - return ( - <> - - - - - ); - -} ----- -endif::hilla-react[] - -.Validation Errors -image::images/bean-validation-errors.gif[A form showing three fields where some of the fields are showing a validation error message] - - -== Defining Custom Constraints - -The Bean Validation standard allows the creation of arbitrary https://beanvalidation.org/2.0-jsr380/spec/#constraintsdefinitionimplementation-constraintdefinition-examples[custom constraints]. The Hilla form `Binder` API also supports such custom constraints. The example that follows shows how to create and use a custom [annotationname]`@StrongPassword` constraint: - -.Defining Custom [annotationname]`@StrongPassword` Constraint -[source,java] ----- -@Retention(RUNTIME) -@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) -@Constraint(validatedBy = { StrongPasswordValidator.class }) -public @interface StrongPassword { - - // min required password strength on the scale from 1 to 5 - int minStrength() default 4; - - String message() default "Please enter a strong password"; - Class[] groups() default { }; - Class[] payload() default { }; -} ----- - -This example defines a validator for the custom [annotationname]`@StrongPassword` constraint: - -[source,java] ----- -public class StrongPasswordValidator implements ConstraintValidator { - - @Override - public boolean isValid(String object, ConstraintValidatorContext constraintContext) { - // Use the zxcvbn library to measure the password strength - Strength strength = zxcvbn.measure(object); - - // fail the validation if the measured strength is insufficient - if (strength.getScore() < minStrength) { - constraintContext - .buildConstraintViolationWithTemplate( - strength.getFeedback().getWarning()) - .addConstraintViolation(); - return false; - } - - return true; - } -} ----- - -This example uses a third-party library to measure the password strength with a custom validator. Add a dependency to your [filename]`pom.xml`: - -[source,xml] ----- - - com.nulab-inc - zxcvbn - 1.3.0 - ----- - -This example uses the custom [annotationname]`@StrongPassword` constraint in a data type: - -[source,java] ----- -public class Employee { - - @StrongPassword - private String password; -} ----- - -No additional steps are needed to start using the new validation rules in forms. The `field()` directive applies all server-side constraints, automatically. - -ifdef::hilla-react[] -.`ProfileView.tsx` -[source,tsx] ----- -import { useForm } from '@vaadin/hilla-react-form'; - -import EmployeeModel from 'Frontend/generated/com/example/application/EmployeeModel'; -import { EmployeeService } from 'Frontend/generated/endpoints'; - -import { Button } from '@vaadin/react-components/Button.js'; -import { PasswordField } from '@vaadin/react-components/PasswordField.js'; - -export default function ProfileView() { - const { model, submit, field } = useForm(EmployeeModel, { - onSubmit: async (e) => { - await EmployeeService.saveEmployee(e); - } - }); - - return ( - <> - - - - ); - -} ----- -endif::hilla-react[] - -However, in this example, validation happens only after the form is submitted. To validate user input immediately, as the user types, you would need to define a validator in TypeScript, as well. The following section shows how to do this. - - -== Defining Custom Client-Side Validators - -To give instant feedback to users as they type, you can define validators in TypeScript, so that they are executed in the browser, without a network round trip. The Hilla form `Binder` API allows you to add validators for both individual fields, and for the form as a whole (e.g., to implement cross-field validation). Client-side validators are executed before the server-side validation is invoked. - -[WARNING] -Validation ALWAYS needs to run on the server in order for your application to be secure. Additionally, you can validate input in the browser immediately the user types, to give a better user experience. - - -=== Adding Validators for a Single Field [[single-field-ts-validators]] - -When a validation rule concerns a single field, a client-side validator should be added with the [methodname]`addValidator()` call on the binder node for that particular field. This is the case with the custom [annotationname]`@StrongPassword` constraint example. - -.Custom Field Validation Error -image::images/custom-validation-errors-ts.gif[A form where a field is showing a customized validation error message] - -ifdef::hilla-react[] -.`ProfileView.tsx` -[source,tsx] ----- -import { useForm, useFormPart } from '@vaadin/hilla-react-form'; -import * as owasp from 'owasp-password-strength-test'; - -import { useEffect } from 'react' - -export default function ProfileView() { - const { model } = useForm(EmployeeModel); - - const passwordField = useFormPart(model.password); - // use effect to make it run only once - useEffect(() => { - passwordField.addValidator({ - message: 'Please enter a strong password', - validate: (password: string) => { - return owasp.test(password); - }, - }); - }, []); - - // ... - -} ----- -endif::hilla-react[] - -This example uses a third-party library to measure the password strength with a custom validator. Add a dependency to your [filename]`package.json`: - -[source,terminal] ----- -npm install --save owasp-password-strength-test -npm install --save-dev @types/owasp-password-strength-test ----- - - -=== Adding Cross-Field Validators - -When a validation rule is based on several fields, a client-side validator should be added with the [methodname]`addValidator()` call on the form binder directly. A typical example where this would be needed is checking that a password is repeated correctly: - -.Custom Field Validation Error -image::images/cross-field-validation-error.png[A form where a field is showing a customized validation error message] - -ifdef::hilla-react[] -[source,tsx] ----- -import { useForm } from '@vaadin/hilla-react-form'; -import { useEffect } from 'react' - -export default function ProfileView() { - - const { model, field, addValidator } = useForm(EmployeeModel); - - useEffect(() => { - addValidator({ - message: 'Please check that the password is repeated correctly', - validate: (value: Employee) => { - if (value.password != value.repeatPassword) { - return [{ property: model.password }]; - } - return []; - } - }); - }, []); - - return ( - <> - - - - ); -} ----- -endif::hilla-react[] - -When record-level validation fails, there are cases when you want to mark several fields as invalid. To do this with the `@vaadin/hilla-lit-form` validator APIs, you can return an _array_ of `{ property, message }` records from the [methodname]`validate()` callback. Returning an empty array is equivalent to returning `true`, meaning that validation has passed. If you need to indicate a validation failure without marking any particular field as invalid, return `false`. - - -== Marking Fields as Required - -To mark a form field as 'required', you can add a [annotationname]`@NotNull` or [annotationname]`@NotEmpty` constraint to the corresponding property in the Java type. [annotationname]`@Size` with a `min` value greater than 0 also causes a field to be required. - -Alternatively, you can set the [propertyname]`impliesRequired` property when adding a custom validator in TypeScript, as shown earlier, in the <> section. - -The fields marked as _required_ have their [propertyname]`required` property set by the `field()` directive. Hence, validation fails if they are left empty. - - -== Built-In Client-Side Validators [[built-in-client-side-validators]] - -The `@vaadin/hilla-lit-form` package provides the client-side validators for the following https://beanvalidation.org/2.0/spec/#builtinconstraints[JSR-380 built-in constraints]: - -. `Email` – The string must be a well-formed email address -. `Null` – Must be `null` -. `NotNull` – Must not be `null` -. `NotEmpty` – Must not be `null` nor empty (must have a `length` property, for example string or array) -. `NotBlank` – Must not be `null` and must contain at least one non-whitespace character -. `AssertTrue` – Must be `true` -. `AssertFalse` – Must be `false` -. `Min` – Must be a number greater than or equal to the specified minimum -- Additional options: `{ value: number | string }` -. `Max` - Must be a number less than or equal to the specified maximum -- Additional options: `{ value: number | string }` -. `DecimalMin` – Must be a number greater than or equal to the specified minimum -- Additional options: `{ value: number | string, inclusive: boolean | undefined }` -. `DecimalMax` – Must be a number less than or equal to the specified maximum -- Additional options: `{ value: number | string, inclusive: boolean | undefined }` -. `Negative` – Must be a negative number (0 is considered to be an invalid value) -. `NegativeOrZero` – Must be a negative number or 0 -. `Positive` – Must be a positive number (0 is considered to be an invalid value) -. `PositiveOrZero` – Must be a positive number or 0 -. `Size` – Size must be in the specified range, inclusive; must have a `length` property, for example a string or an array -- Additional options: `{ min?: number, max?: number }` -. `Digits` – Must be a number within the specified range -- Additional options: `{ integer: number, fraction: number }` -. `Past` – A date string in the past -. `PastOrPresent` – A date string in the past or present -. `Future` – A date string in the future -. `FutureOrPresent` – A date string in the future or present -. `Pattern` – Must match the specified regular expression -- Additional options: `{ regexp: RegExp | string }` - -These are usually used automatically. However, you could also add them to selected fields manually -ifdef::hilla-react[] -by obtaining the [classname]`UseFormPartResult` instance and using the [methodname]`addValidator`; -endif::hilla-react[] -for example, [methodname]`addValidator(new Size({max: 10, message: 'Must be 10 characters or less'}))`. - -All the built-in validators take one constructor parameter, which is usually an optional `options` object with a [propertyname]`message?: string` property (which defaults to `'invalid'`). However, some validators have additional options or support other argument types, instead of the `options` object. - -For example, the `Min` validator requires a `value: number | string` option. This can be given as part of the `options` object. Alternatively, you can pass the minimum value itself, instead of the `options` object (if you don't want to set `message` and leave it as the default `'invalid'`). - -ifdef::hilla-react[] -[source,tsx] ----- -import { Email, Min, NotEmpty, Size } from '@vaadin/hilla-lit-form'; -import { useForm, useFormPart } from '@vaadin/hilla-react-form'; - -export default function ProfileView() { - - const { model, field, addValidator } = useForm(PersonModel); - - const nameField = useFormPart(model.name); - const usernameField = useFormPart(model.username); - const ageField = useFormPart(model.age); - const emailField = useFormPart(model.email); - - useEffect(() => { - nameField.addValidator( - new NotEmpty({ - message: 'Please enter a name' - })); - - usernameField.addValidator( - new Size({ - message: 'Please pick a username 3 to 15 symbols long', - min: 3, - max: 15 - })); - - ageField.addValidator( - new Min({ - message: 'Please enter an age of 18 or above', - value: 18 - })); - - emailField.addValidator(new Email()); - }, []); - - return ( - <> - - - - - - ); -} ----- -endif::hilla-react[] - - -// end::content[] diff --git a/articles/hilla/_shared/guides/forms/_binder.adoc b/articles/hilla/_shared/guides/forms/_binder.adoc deleted file mode 100644 index 48b7055a96..0000000000 --- a/articles/hilla/_shared/guides/forms/_binder.adoc +++ /dev/null @@ -1,107 +0,0 @@ -// tag::content[] - -Hilla provides a way for binding input fields to a data model. - -The client-side [classname]`Binder` supports browser-callable Java services for loading and saving the form data, and reuses the metadata from Java Bean validation annotations for client-side validation. - - -== API Basics - -ifdef::hilla-react[] -The form binding API consists of three key concepts: - -- The [methodname]`field()` directive to bind the field components in form views -- The generated TypeScript models for POJO classes used in the services, which are used as field references and provide the necessary metadata -- The [methodname]`useForm` React Hook returns a [classname]`UseFormResult` object that is responsible for keeping track of the form state, the default and current values, and validation of the data. -endif::hilla-react[] - -See the <> for more details. - - -== How to Bind Form Data - -For example, let us consider a browser-callable Java service with methods for loading and saving a [classname]`Person` bean: - -[source,java] ----- -/** - * A browser-callable service for the person-view.ts form view. - */ -@BrowserCallable -public class PersonService { - /** - * Loads a Person to edit into the view. - * @return default form data - */ - public Person loadPerson() { - // ... - } - - /** - * Saves the edited Person from the view. - * @param person form data to save - */ - public void savePerson(Person person) { - // ... - } -} ----- - -To bind data to a form, follow these steps in your -ifdef::hilla-react[] -[filename]`frontend/views/person/PersonView.tsx` client-side [classname]`React` view: -endif::hilla-react[] - -ifdef::hilla-react[] -. Import the [methodname]`useForm` hook from the `@vaadin/hilla-react-form` package. -Import your [classname]`PersonService` data service and the generated [classname]`PersonModel` from the `frontend/generated` folder: -+ -[source,tsx] ----- -import { useForm } from '@vaadin/hilla-react-form'; - -import { PersonService } from 'Frontend/generated/endpoints'; -import PersonModel from 'Frontend/generated/com/example/application/PersonModel'; ----- - -. Acquire a [classname]`UseFormResult` instance for your view by calling the [methodname]`useForm`: -+ -[source,tsx] ----- -export default function PersonView() { - // ... - - const { model, field } = useForm(PersonModel); - - // ... -} ----- -+ -The [classname]`PersonModel` here is generated alongside a [interfacename]`Person` TypeScript data interface from the [classname]`Person.java` bean. -This describes the structure of the data and the validation-related metadata for the form binding. - -. Bind the UI components in the template using the `{...field()}` syntax: -+ -[source,tsx] ----- -export default function PersonView() { - // ... - - const { model, field } = useForm(PersonModel); - - return ( - - ); - -} ----- -+ -In this example, `model` is an instance of [classname]`PersonModel`. -+ -[NOTE] -Models don't contain any actual data. -To access the actual current or default value of the form, you can acquire their respective reference by destructing the [classname]`UseFormResult` instance as `const { value, defaultValue, ... } = useForm(...)` when calling the [methodname]`useForm` hook. - -endif::hilla-react[] - -// end::content[] diff --git a/articles/hilla/_shared/guides/forms/_binding-arrays.adoc b/articles/hilla/_shared/guides/forms/_binding-arrays.adoc deleted file mode 100644 index d63db2bf4f..0000000000 --- a/articles/hilla/_shared/guides/forms/_binding-arrays.adoc +++ /dev/null @@ -1,108 +0,0 @@ -// tag::content[] - -This page explains working with arrays when building TypeScript form views using form binding. Consider a form for a Java bean that has this structure: - -.`Group.java` -[source,java] ----- -/** - * Example bean with array field - */ -public class Group { - ... - - public Person[] getPeople() { - ... - } - - public void setPeople(Person[] people) { - ... - } -} ----- - - -== Repeating the Array Item Template - -A common need when working with arrays is to iterate over the items and stamp a template for each item. With form binding, array models are iterable. You can use the `useFormArrayPart` hook to iterate over the model and to get and set the value of the array. - -As you also need to get the `field` function for each item using the `useFormPart` hook, you need to create a component for the array item template. - -ifdef::hilla-react[] -[source,tsx] ----- -import { useForm, useFormArrayPart, useFormPart } from '@vaadin/hilla-react-form'; -import { NumberField, TextField } from '@vaadin/react-components'; -import GroupModel from '.../GroupModel'; -import PersonModel from '.../PersonModel'; - -function PersonForm({ model }: { model: PersonModel }) { - const { field } = useFormPart(model); - - return ( -
- - -
- ); -} - -export default function GroupFormView() { - const { field, model } = useForm(GroupModel); - const { items} = useFormArrayPart(model.people); - - return ( - <> - - {items.map((person, index) => ( - - ))} - - ); -} ----- -endif::hilla-react[] - - -== Adding & Removing Array Items - -ifdef::hilla-react[] -You can modify the array value by using the `value` and `setValue` functions provided by `useFormArrayPart`. - -[source,tsx] ----- -import { useForm, useFormArrayPart, useFormPart } from '@vaadin/hilla-react-form'; -import { Button, NumberField, TextField } from '@vaadin/react-components'; -import GroupModel from '.../GroupModel'; -import PersonModel from '.../PersonModel'; - -function PersonForm({ model, remove }: { model: PersonModel, remove: () => void }) { - const { field } = useFormPart(model); - - return ( -
- - - -
- ); -} - -export default function GroupFormView() { - const { field, model } = useForm(GroupModel); - const { items, value, setValue } = useFormArrayPart(model.people); - - return ( - <> - - {items.map((person, index) => ( - setValue(value!.filter((_, i) => i !== index))} /> - ))} - - - ); -} ----- -endif::hilla-react[] - -// end::content[] diff --git a/articles/hilla/_shared/guides/forms/_form-images.adoc b/articles/hilla/_shared/guides/forms/_form-images.adoc deleted file mode 100644 index ed8a686dc5..0000000000 --- a/articles/hilla/_shared/guides/forms/_form-images.adoc +++ /dev/null @@ -1,78 +0,0 @@ -// tag::content[] - -A common use case is a form field that allows an image to be uploaded. For instance, an application may let users customize their avatars. This page shows how this can be implemented for forms, using the `vaadin-upload` component for local file selection (by file browser or dragging), but postponing the actual server upload until the form is submitted. - -The example application allows a contact card to be edited. It assumes a server-side bean where the image is stored as a Base64-encoded string in the field `avatarBase64`: - -[source,java] ----- -include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/forms/Contact.java[] ----- - -It also assumes that the server exposes a browser-callable service for saving updated `Contact` instances: - -[source,java] ----- -include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/forms/ContactService.java[] ----- - -The following form binds the `avatarBase64` field of the instance to a `vaadin-upload` component: - -ifdef::hilla-react[] -[source,tsx] ----- -import ContactModel from 'Frontend/generated/com/vaadin/demo/fusion/forms/ContactModel'; -import { ContactService } from 'Frontend/generated/endpoints'; -import { useForm } from '@vaadin/hilla-react-form'; -import { useEffect } from 'react'; -import { Button } from '@vaadin/react-components/Button.js'; -import { Upload, type UploadBeforeEvent } from '@vaadin/react-components/Upload.js'; -import { readAsDataURL } from 'promise-file-reader'; - - -export default function ContactForm() { - - const form = useForm(ContactModel, { - onSubmit: async (contact) => { - await ContactService.saveContact(contact); - }, - }); - - useEffect(() => { - ContactService.loadContact().then(form.read); - }, []); - - return ( -
- contact's avatar - { - const file = e.detail.file; - e.preventDefault(); - if (form.value) { - form.value.avatarBase64 = await readAsDataURL(file); - } - }} - /> - -
- ); -} ----- -endif::hilla-react[] - -In the above code, the custom `{updloadbefore}` listener prevents `{vaadinupload}` from uploading the received file to the server. Instead, it reads the file into a Base64-encoded string and updates the form field `avatarBase64` via the [classname]`Binder`. The small https://www.npmjs.com/package/promise-file-reader[`promise-file-reader`] library wrapping [classname]`FileReader` inside a promise is used here to handle the result synchronously. - -The [classname]`Contact` instance is first submitted to the [methodname]`saveContact()` service method. This happens through the -ifdef::hilla-react[] -`binder.submit` that is called upon clicking the Save button which executes the provided logic for [methodname]`onSubmit` while obtaining the binder instance. -endif::hilla-react[] - -Only then does the server-side service implementation receive the image string. The server can then choose to recode the image for more efficient storage, if necessary. - -The advantage of using the string type is simplicity; you can use the built-in serialization mechanism of Hilla's form binder and services. This approach isn't suitable for larger files. A streamed upload may be more appropriate. - -// end::content[] diff --git a/articles/hilla/_shared/guides/forms/_form-status-tracking.adoc b/articles/hilla/_shared/guides/forms/_form-status-tracking.adoc deleted file mode 100644 index c7d966572b..0000000000 --- a/articles/hilla/_shared/guides/forms/_form-status-tracking.adoc +++ /dev/null @@ -1,177 +0,0 @@ -// tag::content[] - -You can display some parts of a form. differently -- depending on the form state. You can, for example, disable the [guibutton]*Submit* button when a form has validation errors, or show an _'operation in progress'_ spinner while an async form submission is in progress. - -ifdef::hilla-react[] -[source,tsx] ----- -export default function ProfileView() { - - const form = useForm(EntityModel); - - return ( - <> - ... - - - ); -} ----- -endif::hilla-react[] - -The following properties are available both for the form as a whole, and for each field, independently. They're a part of the <> interface. - -ifdef::hilla-react[] -You can access form status properties for the whole form from the [classname]`UseFormResult` instance returned from [methodname]`useForm`: - -[source,tsx] ----- -const form = useForm(EntityModel); - -const invalid = form.invalid; -const dirty = form.dirty; -const errors = form.errors; -... ----- - -To access form status properties for individual fields, use the [classname]`UseFormPartResult` instance returned from [methodname]`useFormPart`: - -[source,tsx] ----- -const form = useForm(EntityModel); -const nameField = useFormPart(form.model.name); - -const invalid = nameField.invalid; -const dirty = nameField.dirty; -const errors = nameField.errors; ----- -endif::hilla-react[] - -- `dirty`: true if the value of the form or field has been modified by the user. -- `visited`: true if the form or field has been focused by the user. -- `invalid`: true if the form or field has validation errors. -- `required`: true if the form or field is required. -- `errors`: a list of all validation errors for a form or field and its sub-fields. -- `ownErrors`: a list of all the validation errors for a field without sub-fields or for the form, not specific to any field. - -The following properties are available for the form as a whole, but not for individual fields. They're a part of the <> interface. For example, you can access these properties via `binder.validating`. - -- `validating`: true if the form is performing some validation. -- `submitting`: true if the form is submitting the data to a callback. - - -== Example: Disable Form while Submission in Progress - -If form submission could take a long time, it's good to give users indication that something is happening. You may also want to prevent more form submissions until the first one is completed (e.g., in a payment form). - -With the TypeScript `Binder` API, this can be done using the `submitting` property. In the following example, `binder.submitting` is bound to the `disabled` property of the [guibutton]*Submit* button to disable repeating form submissions. It's also used as a condition to render an additional 'submitting' message. - -ifdef::hilla-react[] -[source,tsx] ----- -export default function ProfileView() { - - const { model, submit, field, invalid, submitting } = useForm(PersonModel, { - onSubmit: async (e) => { - await PersonService.sendEntity(e); - } - }); - - return ( - <> - - - - - - - submitting -
-
- - ); -} ----- -endif::hilla-react[] - -image::images/example-show-submitting-status.gif[Disable the form while submission is in progress] - - -== Example: List Validation Errors - -Sometimes you may want to list all validation errors in one place. This is convenient especially in large forms, where it can be difficult to find the one field that failed the validation. - -With the TypeScript `Binder` API, this can be done using the `errors` property. In the following example, the form template iterates over the `form.errors` list and renders the full list under the form. - -ifdef::hilla-react[] -[source,tsx] ----- -
- {form.errors.map(error => ( - <> -
{error.property as string}
-
{error.message as string}
- - ))} -
----- -endif::hilla-react[] - -image::images/example-list-all-validation-errors.gif[Disable the form while submission is in progress] - - -ifdef::hilla-react[] - -== Example: Populate a Field Based on Another - -Sometimes you may want to populate a field based on the value of another field. For example, you may want to set the value of a `city` select field based on the value of a `country` select field. - -In such cases, you can use the `useEffect` Hook to react to changes in the value of the first field, and update the value of the second field, accordingly. The following simplified example shows how to do this: - -[source,tsx] ----- -const form = useForm(MyModel); -useEffect(() => { - // Do something here to change the value of the second - // field based on the first field value -}, [form.value.firstField]); ----- - -For a more complete example, open the following section. - -.Interdependent Fields Example -[%collapsible] -==== -[.example] --- -[source,tsx] -.CompanyOfficeView.tsx ----- -include::{root}{root-fix}/frontend/demo/fusion/forms/formchange/CompanyOfficeView.tsx[tags=snippet,indent=0] ----- -[source,java] -.`OfficeService.java` ----- -include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/forms/formchange/OfficeService.java[] ----- -[source,java] -.`CompanyOffice.java` ----- -include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/forms/formchange/CompanyOffice.java[] ----- -[source,java] -.`Country.java` ----- -include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/forms/formchange/Country.java[] ----- -[source,java] -.`City.java` ----- -include::{root}{root-fix}/src/main/java/com/vaadin/demo/fusion/forms/formchange/City.java[] ----- --- -==== - -endif::hilla-react[] - -// end::content[] diff --git a/articles/hilla/_shared/guides/forms/_reference.adoc b/articles/hilla/_shared/guides/forms/_reference.adoc deleted file mode 100644 index 14bd1654d0..0000000000 --- a/articles/hilla/_shared/guides/forms/_reference.adoc +++ /dev/null @@ -1,391 +0,0 @@ -// tag::content[] - -The key concepts behind form binding are the [methodname]`field()` directive, the [classname]`Model`, the [classname]`Binder` and binder nodes. - - -== The Field Directive - -The [methodname]`field()` directive does the main task of binding the form field components in the template by doing the following: - -- sets the `name` attribute; -- implements two-way binding for the value state; -- sets the `required` (boolean) state; and -- sets the `invalid` (boolean) state and the `errorMessage` (string) when the current value is invalid. - -ifdef::hilla-react[] -.Using the Field Directive -[source,tsx] ----- -import { TextField } from '@vaadin/react-components/TextField.js'; -... -const { model, field } = useForm(PersonModel); -... -return ( - -); ----- -endif::hilla-react[] - -Depending on the type of the bound component, the field directive selects a strategy that defines exactly how the states above are applied on the component, for example which attributes and properties of the element are used. - -The field directive supports Vaadin components and HTML input elements. Vaadin components have support for all of the states. However, for HTML input elements, the `invalid`, `required` and `errorMessage` states aren't displayed in the bound component. As a workaround, you can manually bind these in the template: - -ifdef::hilla-react[] -[source,tsx] ----- -import { useForm, useFormPart } from '@vaadin/hilla-react-form'; -import { StringModel } from '@vaadin/hilla-lit-form'; -... -interface FullNameProps { - fullNameModel: StringModel; -} - -function FullNameComponent({ fullNameModel }: FullNameProps) { - - const { model, field, required, errors, invalid } = useFormPart(fullNameModel); - - return ( - <> - - -
- - - {errors[0]?.message} - - - - ); -} - -export default function Main() { - const { model } = useForm(PersonModel); - return ; -} ----- -endif::hilla-react[] - -See the <<{articles}/hilla/guides/forms/vaadin-components#, Binding Data to Hilla Components>> for more, related information. - - - -== The Form Model - -A form model describes the structure of the form. It allows individual fields to be referenced in a type-safe way, and is an alternative to strings such as `'person.firstName'`. - -Typically, a model is used as an argument for the [methodname]`field()` directive -to specify the target form property to create a binding or to access the state. In contrast to string names such as `'person.firstName'`, typed form models allow autocompletion and static type checking, which makes creating forms faster and safer. - -Hilla automatically generates Model classes for server-side services from Java beans. There's usually no need to define models, manually. - -Technically, every model instance represents either a key of a parent model, or the value of the [classname]`Binder` itself (e.g., the form data object). - -[NOTE] -The model classes aren't intended to be instantiated manually. Instead, the [classname]`Binder` constructor receives the form model class and takes care of creating model instances. - - -=== Primitive Models - -These are the built-in models that represent the common primitive field types: - -|=== -| Type | Value type `T` | Empty Value ([methodname]`Model.createEmptyValue()` result) - -| [classname]`StringModel` | `string` | `''` -| [classname]`NumberModel` | `number` | `0` -| [classname]`BooleanModel` | `boolean` | `false` -|=== - -Primitive models extend [classname]`PrimitiveModel`. Primitive models are leaf nodes of the data structure; they don't have nested field keys. - - -=== Object Models - -The primitive editable values of the form are typically grouped into objects. This is accommodated through [@classname]`ObjectModel`, which is also a common superclass for all the models generated from Java beans. - -The subclasses of [classname]`ObjectModel` define the type argument constraints and the default type. In addition, the subclasses list all the public properties, following the shape of the described object. - -For example, for the following Java bean: - -.`IdEntity.java` -[source,java] ----- -public class IdEntity { - private String idString; - - public String getIdString() { - return idString; - } - - public void setIdString(String idString) { - this.idString = idString; - } -} ----- - -.`Person.java` -[source,java] ----- -import jakarta.validation.constraints.NotEmpty; - -public class Person extends IdEntity { - @NotEmpty(message = "Cannot be empty") - private String fullName; - - public String getFullName() { - return fullName; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } -} ----- - -The following TypeScript interfaces are generated to type-check the services: - -.`IdEntity.ts` -[source,typescript] ----- -export default interface IdEntity { - idString: string | undefined; -} ----- - -.`Person.ts` -[source,typescript] ----- -import IdEntity from './IdEntity'; - -export default interface Person extends IdEntity { - fullName: string | undefined; -} ----- - -The following models are generated for form binding: - -.`IdEntityModel.ts` -[source,typescript] ----- -import IdEntity from './IdEntity'; - -export default class IdEntityModel extends ObjectModel { - static createEmptyValue: () => IdEntity; - readonly idString = new StringModel(this, 'idString'); -} ----- - -.`PersonModel.ts` -[source,typescript] ----- -import IdEntityModel from './IdEntityModel'; - -import Person from './Person'; - -export default class PersonModel extends IdEntityModel { - static createEmptyValue: () => Person; - readonly fullName = new StringModel(this, 'fullName', new NotEmpty({message: 'Cannot be empty'})); -} ----- - -[CAUTION] -To avoid naming collisions with user-defined object model fields, the built-in models and model superclasses don't have any public instance properties or methods, aside from the [methodname]`toString()` and [methodname]`valueOf()` methods inherited from [classname]`AbstractModel` (see following). - -The properties of object models are intentionally read-only. - - -=== Array Model - -[classname]`ArrayModel` is used to represent array properties. - -The type argument `T` in array models indicates the type of values in the array. - -An array model instance contains the item model class reference. The item model is instantiated for every array entry, as necessary. - -Array models are iterable. Iterating yields binder nodes for entries: - -ifdef::hilla-react[] -[source,tsx] ----- -import { TextField } from '@vaadin/react-components/TextField.js'; - -interface PersonProps { - model: PersonModel; -} - -function Person({ model }: PersonProps) { - const { field, model, value } = useFormPart(model); - - return ( -
- - Full name: - {value.fullName} -
- ); -} -... -return ( - <> - {model.people.map(person => )} - -); ----- -endif::hilla-react[] - -The array entries aren't available for indexing with bracket notation (`[]`). - - -=== Abstract Model Superclass - -All models subclass from the [classname]`AbstractModel` TypeScript class, where the `T` type argument refers to the value type. - - -==== Empty Value Definition - -Model classes define an empty value, which is used to initialize the `defaultValue` and `value` properties, and also for [methodname]`clear()`. - -For this purpose, [classname]`AbstractModel`, as well as every subclass, has a method [methodname]`static createEmptyValue(): T`, which returns the empty value of the subject model type. - -[source,typescript] ----- -const emptyPerson: Person = PersonModel.createEmptyValue(); -console.log(emptyPerson); // {"fullName": ""} ----- - - -==== Models in Expressions - -As with any JavaScript object, [classname]`AbstractModel` has [methodname]`toString(): string` and [methodname]`valueOf(): T` instance methods, -ifdef::hilla-react[] -but, as we know that the model is just metadata, they cannot return any values. Then, those instance methods must be called on the `value` property obtained from calling [classname]`useForm` instead: -endif::hilla-react[] - -ifdef::hilla-react[] -[source,tsx] ----- -const { model, value } = useForm(PersonModel); -... -return ( - <> - {value.name.toString()} - {value.name.valueOf()} - {value.name} - -) ----- - -Then, it's possible to use the values in formulas using either of the following: - -[source,tsx] ----- -return ( - <> - Cost: {value.quantity.valueOf() * value.price.valueOf()} - Cost: {value.quantity * value.price} - -); ----- -endif::hilla-react[] - - -== The Binder [[binder]] - -A form binder controls all aspects of a single form. It's typically used to get and set the form value, access the form model, validate, reset, and submit the form. - -The [classname]`Binder` constructor arguments are: - -`Model: ModelConstructor`:: -The constructor (class reference) of the form model. -The [classname]`Binder` instantiates the top-level model; and -`config?: BinderConfiguration`:: -The options object. - `onChange?: (oldValue?: T) => void`::: - The callback that updates the form view; - `onSubmit?: (value: T) => Promise`::: - The service method to submit the form data to. - -The [classname]`Binder` has the following instance properties: - -`model: M`:: -The form model, the top-level model instance created by the [classname]`Binder`. -`value: T`:: -The current value of the form, two-way bound to the field components. -`defaultValue: T`:: -The initial value of the form, before any fields are edited by the user. -`readonly validating: boolean`:: -True when there is an ongoing validation. -`readonly submitting: boolean`:: -True if the form was submitted, but the submit promise isn't resolved yet. - -The [classname]`Binder` instance methods are: - -`read(value: T): void`:: -Load the given value to the form. -`reset(): void`:: -Reset the form to the previous value. -`clear(): void`:: -Sets the form to empty value, as defined in the Model. -`getFieldStrategy(element: any): FieldStrategy`:: -Determines and returns the `field` directive strategy for the bound element. Override to customize the binding strategy for a component. The [classname]`Binder` extends [classname]`BinderNode`; see the inherited properties and methods that follow. - - - -== Binder Nodes [[binder-node]] - -The [classname]`BinderNode` class provides the form-binding-related APIs with respect to a particular model instance. - -Structurally, model instances form a tree in which the object and array models have child nodes of field and array item model instances. - -Every model instance has a one-to-one mapping to a corresponding [classname]`BinderNode` instance. The [classname]`Binder` itself is a [classname]`BinderNode` for the top-level form model. -The binder nodes have the following properties: - -`model: M`:: -The model instance mapped to this binder node. -`value: T`:: -The current value related to the model, two-way bound to the field components. -`readonly defaultValue: T`:: -The default value related to the model. -Note: this is read-only here; use the top-level `binder.defaultValue` to change. -`parent: BinderNode> | undefined`:: -The parent node, if this binder node corresponds to a nested model; otherwise, `undefined` for the top-level binder. -`binder: Binder>`:: -The binder for the top-level model. -`readonly name: string`:: -The name generated from the model structure, used to set the `name` attribute on the field components. -`readonly required: boolean`:: -True if the value is required to be non-empty. -Based on the presence of validators that have the `impliesRequired: true` flag. -`dirty: boolean`:: -True if the current `value` is different from the `defaultValue`. -`visited: boolean`:: -True if the bound field was ever focused and blurred by the user. The value is set by the `field` directive. -`validators: ReadonlyArray>`:: -The array of validators for the model. -The default value is defined in the model. -`readonly ownErrors: ReadonlyArray>`:: -The array of validation errors directly related with the model. -`readonly errors: ReadonlyArray>`:: -The combined array of all errors for this node's model and all its nested models. -`readonly invalid: boolean`:: -True when the `errors` array isn't empty. - -The binder node has the following instance methods: - -`for>(model: NM): BinderNode, NM>`:: -Returns a binder node for the nested model instance. -`async validate(): Promise>>`:: -Runs all validation callbacks potentially affecting this or any nested model. Returns the combined array of all errors, as in the `errors` property. -`addValidator(validator: Validator): void`:: -A helper method to add a validator to the `validators`. -`appendItem(itemValue?: T): void`:: -A helper method for array models. If the node's model is an [classname]`ArrayModel`, appends an item to the array; otherwise throws an exception. If the argument is given, the argument value is used for the new item; otherwise, a new empty item is created. -`prependItem(itemValue?: T): void`:: -A helper method for array modes, similar to [methodname]`appendItem()`, but prepends an item to the array. -`removeSelf(): void`:: -A helper method for array item models. If the node's **parent model** is an [classname]`ArrayModel`, removes the item the array; otherwise throws an exception. - -// end::content[] diff --git a/articles/hilla/_shared/guides/forms/_vaadin-components.adoc b/articles/hilla/_shared/guides/forms/_vaadin-components.adoc deleted file mode 100644 index 3a53ff9ccd..0000000000 --- a/articles/hilla/_shared/guides/forms/_vaadin-components.adoc +++ /dev/null @@ -1,359 +0,0 @@ -// tag::content[] - -Client <> supports out of the box the set of form elements present in Hilla components. This means that data binding, type mapping, required flag, and validation messages can function without any extra work. - - -== Configuring Server Data & Service - -When defining Bean objects in Java, apart from the type, you can define validation and error messages. - -For the examples here, the code on the client side is based on the data and services defined in the following Java classes: - -[source,java] ----- -public class MyEntity { - @AssertTrue(message = "Please agree this") - Boolean myBooleanField = false; - - @NotEmpty(message = "Select at least one option") - List myListField = Arrays.asList("item-1"); - - @Pattern(regexp = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}", - message = "must be 8+ characters, with uppercase, lowercase, and numbers") - String myPasswordField = "bar"; - - @Email(message = "must be a valid email address") - String myEmailField = "foo@bar.baz"; - - @PositiveOrZero(message = "Should be positive or zero") - Integer myIntegerField = 12; - @Positive(message = "Should be positive") - Double myDoubleField = 12.33d; - - @Future(message = "Should be a date in the future") - LocalDate myDateField = LocalDate.now().plusDays(1); - LocalDateTime myDateTimeField = LocalDateTime.now(); - LocalTime myTimeField = LocalTime.now(); - - @Min(0) @Max(1) - Integer mySelectField = 1; -} - -@BrowserCallable -public class MyService { - public MyEntity getMyEntity() { - return new MyEntity(); - } - - public List getMyOptions() { - return Arrays.asList("item-1", "item-2", "item-3"); - } -} ----- - - -== Configuring Client Fields - -Before using form binders, you need to import your entity models and instantiate the binder. You also need to perform the service call to get the entity value, as explained in the articles <> and <>. - -For the examples in this article, these are the significant lines needed in the view: - -ifdef::hilla-react[] -[source,tsx] ----- -import { MyService } from 'Frontend/generated/MyService'; -import MyEntityModel from 'Frontend/generated/com/example/MyEntityModel'; -... -const form = useForm(MyEntityModel); -... -useEffect(() => { - MyService.getMyEntity().then(form.read); -}, []); ----- -endif::hilla-react[] - - -=== Binding Data to Text & Number Fields - -No extra action is needed to configure the Vaadin components that represent Text & Number values. - -ifdef::hilla-react[] -[source,tsx] ----- -import { EmailField } from '@vaadin/react-components/EmailField.js'; -import { IntegerField } from '@vaadin/react-components/IntegerField.js'; -import { NumberField } from '@vaadin/react-components/NumberField.js'; -import { PasswordField } from '@vaadin/react-components/PasswordField.js'; -import { TextArea } from '@vaadin/react-components/TextArea.js'; -import { TextField } from '@vaadin/react-components/TextField.js'; - -... -const { model, field } = useForm(MyEntityModel); -... -return ( - <> - - - - - -