Oorian provides two form classes that share the same validation framework: Form for standard HTML forms, and OorianForm for forms that submit through Oorian's real-time communication channel. Both support built-in validation with ValidatedInput, cross-field validation, and data binding. This guide covers both approaches and shows you when to use each one.
The Form Element
Oorian's Form class is a full-featured Java representation of the HTML <form> element. It supports all standard form attributes — action URL, HTTP method, encoding type, autocomplete — and can be used for traditional form submissions that navigate to a new page:
// Traditional form that submits to a URL
Form form = new Form();
form.setAction("/api/register");
form.setMethod(FormMethod.POST);
form.setEncType(FormEncoding.MULTIPART_FORM_DATA);
TextInput nameInput = new TextInput();
nameInput.setName("name");
form.addElement(nameInput);
TextInput emailInput = new TextInput();
emailInput.setName("email");
form.addElement(emailInput);
form.addElement(new Button("Register"));
For single-page applications where you want to handle submissions without a page reload, call setDynamic() and register a FormListener:
Form form = new Form();
form.setDynamic();
form.registerListener(this, FormEvent.class);
// ... add inputs ...
@Override
public void onEvent(FormEvent event)
{
Parameters params = event.getParameters();
String name = params.getParameterValue("name");
// Process the form data via AJAX
}
The Form class is the right choice when you need to post data to an external endpoint, submit to a traditional servlet, or handle file uploads with multipart encoding.
OorianForm: Seamless Server Integration
OorianForm extends Form and integrates with Oorian's communication system. When submitted, it sends form data through Oorian's active channel (WebSocket or SSE) instead of making a separate HTTP request. This means form submissions are handled by the same connection that manages all other page interactions — no additional round trips, no page reloads:
@Page("/contact")
public class ContactPage extends HtmlPage implements FormListener
{
private TextInput nameInput;
private TextInput emailInput;
private TextArea messageInput;
private Span statusLabel;
@Override
protected void createBody(Body body)
{
OorianForm form = new OorianForm();
form.registerListener(this, FormEvent.class);
nameInput = new TextInput();
nameInput.setName("name");
form.addElement(nameInput);
emailInput = new TextInput();
emailInput.setName("email");
form.addElement(emailInput);
messageInput = new TextArea();
messageInput.setName("message");
form.addElement(messageInput);
form.addElement(new Button("Send Message"));
statusLabel = new Span();
form.addElement(statusLabel);
body.addElement(form);
}
@Override
public void onEvent(FormEvent event)
{
Parameters params = event.getParameters();
String name = params.getParameterValue("name");
String email = params.getParameterValue("email");
String message = params.getParameterValue("message");
// Process the form data
sendContactEmail(name, email, message);
statusLabel.setText("Thank you! Your message has been sent.");
sendUpdate();
}
}
OorianForm also supports programmatic submission via its submit() method, which sends a command to the client to trigger form submission from Java code. Use OorianForm for most Oorian applications — it's the natural choice when your page is already using Oorian's communication for events and UI updates.
When to Use Each
| Scenario | Use |
|---|---|
| Posting to an external API endpoint | Form |
| Traditional form submission with page navigation | Form |
| AJAX submission without Oorian's channel | Form + setDynamic() |
| Standard Oorian application forms | OorianForm |
| Forms that update the page after submission | OorianForm |
| Programmatic form submission from Java | OorianForm |
The Parameters Class
The Parameters class provides typed access to form values. All values arrive as strings from the browser, and Parameters handles type conversion for you:
| Method | Returns |
|---|---|
getParameterValue(name) | String — the first value, or null |
getParameterValueAsInt(name) | Integer — parsed integer, or null |
getParameterValueAsLong(name) | Long — parsed long, or null |
getParameterValueAsDouble(name) | Double — parsed double, or null |
getParameterValueAsFloat(name) | Float — parsed float, or null |
getParameterValueAsBoolean(name) | Boolean — parsed boolean, or false |
getParameterValues(name) | List<String> — all values (for multi-select, checkboxes) |
containsParameter(name) | boolean — true if the parameter exists |
Parameters params = event.getParameters();
// String value
String name = params.getParameterValue("name");
// Numeric values with automatic parsing
Integer age = params.getParameterValueAsInt("age");
Double salary = params.getParameterValueAsDouble("salary");
// Boolean (checkbox)
Boolean subscribe = params.getParameterValueAsBoolean("subscribe");
// Multi-value (checkboxes, multi-select)
List<String> selectedRoles = params.getParameterValues("roles");
// Check if parameter was submitted
if (params.containsParameter("agreeToTerms"))
{
// User checked the terms checkbox
}
Form Validation
Validation is built directly into the Form class, which means it works with both Form and OorianForm. Each input field is wrapped in a ValidatedInput that defines validation rules through a fluent API:
// Validation works on both Form and OorianForm
OorianForm form = new OorianForm();
form.registerListener(this, FormEvent.class);
// Create input elements
TextInput emailInput = new TextInput();
emailInput.setName("email");
Span emailError = new Span();
PasswordInput passwordInput = new PasswordInput();
passwordInput.setName("password");
Span passwordError = new Span();
// Register validated inputs with rules
form.addValidatedInput(
new ValidatedInput<>(emailInput, emailError)
.required("Email is required")
.email("Please enter a valid email address")
);
form.addValidatedInput(
new ValidatedInput<>(passwordInput, passwordError)
.required("Password is required")
.minLength(8, "Password must be at least 8 characters")
);
// Add elements to the form
form.addElement(emailInput);
form.addElement(emailError);
form.addElement(passwordInput);
form.addElement(passwordError);
form.addElement(new Button("Sign In"));
Each ValidatedInput takes the input element and an error display element (typically a Span). When validation fails, the error message is automatically shown in the error display element, and CSS classes are applied for visual feedback.
Built-in Validators
Oorian includes a comprehensive set of validators that cover common form validation scenarios:
| Validator Method | Description |
|---|---|
required(message) | Field must not be empty |
email(message) | Must be a valid email address |
minLength(n, message) | Minimum character length |
maxLength(n, message) | Maximum character length |
length(min, max, message) | Character length must be within range |
pattern(regex, message) | Must match a regular expression pattern |
range(min, max, message) | Numeric value must be within range |
url(message) | Must be a valid URL |
phone(format, message) | Must match a phone number format |
numeric(message) | Must be a numeric value |
alphanumeric(message) | Must contain only letters and numbers |
creditCard(message) | Must be a valid credit card number |
date(format, message) | Must match a date format |
Validators are chained using the fluent API. They are evaluated in the order they are added, and validation stops at the first failure for each field:
new ValidatedInput<>(usernameInput, usernameError)
.required("Username is required")
.minLength(3, "Username must be at least 3 characters")
.maxLength(20, "Username must be 20 characters or fewer")
.alphanumeric("Username must contain only letters and numbers");
Handling Validation in onEvent
In your FormListener, call form.validate(event) to validate all fields at once. The returned ValidationResult tells you whether the form is valid, and error messages are automatically displayed next to each invalid field:
@Override
public void onEvent(FormEvent event)
{
ValidationResult result = form.validate(event);
if (result.isValid())
{
// All fields passed validation - process the form
Parameters params = event.getParameters();
String email = params.getParameterValue("email");
String password = params.getParameterValue("password");
createAccount(email, password);
}
// sendUpdate() pushes validation error messages to the browser
sendUpdate();
}
Validation Groups
Sometimes the same form is used for different operations — creating a new record, updating an existing one, or saving a draft. Validation groups let you run different sets of validators depending on the operation. The Form class provides convenience methods for the most common scenarios:
// Validate for a create operation
ValidationResult result = form.validateForCreate(event);
// Validate for an update operation
ValidationResult result = form.validateForUpdate(event);
// Validate for saving a draft (lenient rules)
ValidationResult result = form.validateForDraft(event);
// Or specify custom groups
ValidationResult result = form.validate(event, MyCustomGroup.class);
Cross-Field Validation
For validation rules that compare two or more fields, use CompareValidator as a form-level validator. This is commonly used for password confirmation and date range validation:
// Password confirmation
form.addFormValidator(
new CompareValidator("password", "confirmPassword",
CompareValidator.Operation.EQUALS)
.withMessage("Passwords do not match")
);
// Date range validation
form.addFormValidator(
new CompareValidator("startDate", "endDate",
CompareValidator.Operation.LESS_THAN)
.withMessage("Start date must be before end date")
);
The CompareValidator.Operation enum supports: EQUALS, NOT_EQUALS, LESS_THAN, LESS_THAN_OR_EQUALS, GREATER_THAN, and GREATER_THAN_OR_EQUALS.
Data Binding
Oorian's data binding framework connects Java bean properties to form fields, eliminating the manual work of reading values from forms and writing them to objects. The Binder class handles type conversion, validation, and bidirectional synchronization between your model and the UI.
Type Converters
Type converters transform values between their UI presentation type (typically String) and their Java model type. Every converter implements the Converter<P, M> interface with two methods:
convertToModel(P value)— converts the presentation value to the model type. ThrowsConversionExceptionon invalid input.convertToPresentation(M value)— converts the model value to the presentation type. Never throws.
Oorian includes built-in converters for common types:
| Converter | From | To |
|---|---|---|
StringToIntegerConverter | String | Integer |
StringToLongConverter | String | Long |
StringToDoubleConverter | String | Double |
StringToBigDecimalConverter | String | BigDecimal |
StringToBooleanConverter | String | Boolean |
StringToDateConverter | String | Date |
StringToLocalDateConverter | String | LocalDate |
StringToLocalDateTimeConverter | String | LocalDateTime |
StringToEnumConverter | String | Enum |
Built-in converters are registered in the ConverterRegistry by default. You can register custom converters for application-specific types:
ConverterRegistry.getInstance().register(
String.class, Money.class, new StringToMoneyConverter()
);
The Binder
The Binder<BEAN> class manages the bindings between form fields and bean properties. Use the fluent BindingBuilder API to configure each binding with converters, validators, and required-field checks:
Binder<User> binder = new Binder<>(User.class);
// Bind a text input to a String property
binder.forField(nameInput)
.asRequired("Name is required")
.bind("name");
// Bind with a type converter
binder.forField(ageInput)
.withConverter(new StringToIntegerConverter("Must be a number"))
.bind("age");
// Bind with converter and validator
binder.forField(emailInput)
.asRequired("Email is required")
.withValidator(v -> v.contains("@"), "Invalid email")
.bind("email");
// Bind a checkbox to a boolean property
binder.forCheckbox(activeCheckbox)
.bind("active");
// Bind a select to an enum property
binder.forField(roleSelect)
.withConverter(new StringToEnumConverter<>(Role.class))
.bind("role");
Read values from a bean into the form, and write form values back to a bean:
// Populate the form from a bean
User user = loadUser(userId);
binder.readBean(user);
// Write form values back to the bean
// Throws BindingValidationException if any binding fails
binder.writeBean(user);
// Or write only if all bindings are valid (returns boolean)
if (binder.writeBeanIfValid(user))
{
saveUser(user);
}
// Check if any field has been modified
boolean dirty = binder.isModified();
Auto-Binding
For forms where field names match bean property names, use bindInstanceFields() to automatically bind fields by name. The binder scans the target object for fields whose names match bean properties and creates bindings with automatic converter lookup:
public class UserForm extends OorianForm
{
private TextInput name; // matches User.name
private TextInput email; // matches User.email
private Checkbox active; // matches User.active
public void setupBindings(Binder<User> binder)
{
// Automatically binds name, email, and active
binder.bindInstanceFields(this);
}
}
Fields that don't match any bean property are silently skipped.
Validation Pipeline
When writing form values to a bean, the binder runs each binding through a three-stage validation pipeline:
- Required check — if
asRequired()was called and the field is empty, validation stops with the required message - Type conversion — the converter's
convertToModel()is called; aConversionExceptionstops validation - Validators — each registered validator runs in order; the first failure stops the chain
Writes are atomic: if any binding fails validation, no values are written to the bean. This prevents partial updates where some fields are written and others are not.
Complete Example
Here is a complete example combining forms, data binding, and validation in a single page:
public class EditUserPage extends HtmlPage implements FormListener
{
private Binder<User> binder;
private User user;
@Override
protected void createBody(Body body)
{
user = loadUser(getUrlParameters().getValue("id"));
binder = new Binder<>(User.class);
OorianForm form = new OorianForm();
TextInput nameInput = new TextInput();
binder.forField(nameInput)
.asRequired("Name is required")
.bind("name");
form.addElement(nameInput);
TextInput emailInput = new TextInput();
binder.forField(emailInput)
.asRequired("Email is required")
.withValidator(v -> v.contains("@"), "Invalid email")
.bind("email");
form.addElement(emailInput);
TextInput ageInput = new TextInput();
binder.forField(ageInput)
.withConverter(new StringToIntegerConverter("Must be a number"))
.withValidator(v -> v >= 0 && v <= 150, "Invalid age")
.bind("age");
form.addElement(ageInput);
SubmitButton submit = new SubmitButton("Save");
form.addElement(submit);
form.registerListener(this, FormEvent.class);
// Populate the form
binder.readBean(user);
body.addElement(form);
}
@Override
public void onEvent(FormEvent event)
{
if (binder.writeBeanIfValid(user))
{
saveUser(user);
navigateTo("/users");
}
}
}