Bindings
A binding in webforJ links a specific property of a Java Bean to a UI component. This linkage enables automatic updates between the UI and the backend model. Each binding can handle data synchronization, validation, transformation, and event management.
You can initiate bindings only through the BindingContext
. It manages a collection of binding instances, each linking a UI component to
a property of a bean. It facilitates group operations on bindings, such as validation and
synchronization between the UI components and the bean's properties. It acts as an aggregator,
allowing for collective actions on multiple bindings, thereby streamlining the management of data
flow within applications.
This section introduces the basics of manually configuring bindings. Additionally, you can automatically create bindings based on the UI components in your form. Once you grasp the fundamentals, learn more by reading the Automatic Binding section.
Configure bindings
Start by creating a new instance of BindingContext
which manages all bindings for a particular model.
This context ensures that all bindings can be validated and updated collectively.
BindingContext<Hero> context = new BindingContext<>(Hero.class);
Each form should have only one BindingContext
instance, and you should use this instance for all the components in the form.
The bound property
A binding property is a specific field or attribute of a Java Bean that can be linked to a UI component in your app. This linkage allows changes in the UI to directly affect the corresponding property of the data model, and vice versa, facilitating a reactive user experience.
When setting up a binding, you should provide the property name as a string. This name must match the field name in the Java Bean class. Here's a simple example:
BindingContext<Hero> context = new BindingContext<>(Hero.class, true);
context
.bind(textField, "power")
.add()
public class Hero {
private String name;
private String power;
// setters and getters
}
the bind
methods returns a BindingBuilder
which creates the Binding
object and you can use to configure the binding several settings, the add
method which
is what actually gets the binding added to the context.
The bound component
The other side of the binding is the bound component, which refers to the UI component that interacts with the property of the Java Bean.
The bound component can be any UI component that supports user interaction and display, such as text fields, combo boxes, checkboxes, or
any custom component that implements the ValueAware
interface.
The bound component serves as the user's point of interaction with the underlying data model. It displays data to the user and also captures user inputs which are then propagated back to the model.
TextField nameTextField = new TextField("Name");
BindingContext<Hero> context = new BindingContext<>(Hero.class);
context.bind(nameTextField, "name").add();
Reading and writing data
Reading data
Reading data involves populating UI components with values from the data model.
This is typically done when a form is initially displayed, or when you need to reload the data due to changes in the underlying model.
The read
method provided by BindingContext
makes this process straightforward.
// Assume Hero object has been instantiated and initialized
Hero hero = new Hero("Clark Kent", "Flying");
// BindingContext is already configured with bindings
context.read(hero);
In this example, the read
method takes an instance of Hero
and updates all bound UI components to reflect the hero's properties.
If the hero's name or power changes, the corresponding UI components (like a TextField
for name and a ComboBox
for powers)
display these new values.
Writing data
Writing data involves collecting values from the UI components and updating the data model.
This typically occurs when a user submits a form. The write
method handles validation and model updating in one step.
// This could be triggered by a form submission event
submit.onClick(event -> {
ValidationResult results = context.write(hero);
if (results.isValid()) {
// Data is valid, and hero object has been updated
// repository.save(hero);
} else {
// Handle validation errors
// results.getMessages();
}
});
In the code above, when the user clicks the submit button, the write
method is called.
It performs all configured validations and, if the data passes all checks, updates the Hero
object
with new values from the bound components.
If the data is valid, you might save to a database or processed further. If there are validation errors,
you should handle appropriately, typically by displaying error messages to the user.
All core components of webforJ have default configurations to automatically report validation errors, either inline or through a popover. You can customize this behavior using Reporters.
ReadOnly data
In certain scenarios, you may want your app to display data without allowing the end-user to modify it directly through the UI. This is where read-only data bindings become crucial. webforJ supports the configuration of bindings to be read-only, ensuring that you can display data, but not edit it through bound UI components.
Configuring readonly bindings
To set up a read-only binding, you can configure the binding to turn off or ignore UI component input. This ensures that the data remains unchanged from the UI perspective, while still updating programmatically if needed.
// Configuring a text field to be read-only in the binding context
BindingContext<Hero> context = new BindingContext<>(Hero.class);
context.bind(nameTextField, "name")
.readOnly()
.add();
In this configuration, readOnly
ensures that the nameTextField
doesn't accept user input, effectively making the text field display
the data without allowing modifications.
The binding can mark the component as read-only only if the UI components implements the ReadOnlyAware
interface.
It's important to differentiate between bindings you configure as read-only and UI components you set to display as read-only. When you mark a binding as read-only, it impacts how the binding manages data during the write process, not just the UI behavior.
When you mark a binding as read-only, the system skips data updates. Any changes to the UI component won't transmit back to the data model. This ensures that even if the UI component somehow receives user input, it won't update the underlying data model. Maintaining this separation is crucial for preserving data integrity in scenarios where user actions shouldn't alter the data.
In contrast, setting a UI component as read-only, without configuring the binding itself as read-only, simply stops the user from making changes to the UI component but doesn't stop the binding from updating the data model if changes occur programmatically or through other means.
Binding getters and setters
Setters and getters are methods in Java that set and get the values of properties, respectively. In the context of data binding, they are used to define how properties are updated and retrieved within the binding framework.
Customizing setters and getters
Although webforJ can automatically use standard JavaBean naming conventions
(for example, getName()
, setName()
for a property name
), you might need to define custom behavior.
This is necessary when the property doesn't follow the conventional naming or when the data handling requires additional logic.
Using custom getters
Custom getters are used when the value retrieval process involves more than just returning a property. For example, you might want to format the string, compute a value, or log certain actions when a property is accessed.
BindingContext<Hero> context = new BindingContext<>(Hero.class);
context
.bind(textField, "power")
.useGetter(hero -> {
String name = hero.getName();
return name.toUpperCase(); // Custom logic: convert name to uppercase
});
Using custom setters
Custom setters come into play when setting a property involves additional operations such as validation, transformation, or side effects like logging or notifying other parts of your app.
BindingContext<Hero> context = new BindingContext<>(Hero.class);
context
.bind(textField, "power")
.useSetter((hero, name) -> {
System.out.println("Updating name from " + hero.getName() + " to " + name);
hero.setName(name); // Additional operation: logging
});