Forms
JWeb's Form DSL provides Java methods for form elements with HTML5 validation. Handle form events with state or submit to server handlers.
Overview
Create forms with the form() element and various input elements. Use attrs() to set attributes like name, type, and validation rules.
form(attrs().action("/submit").method("POST"),
input(attrs().type("text").name("name").required()),
input(attrs().type("email").name("email").required()),
button(attrs().type("submit"), text("Submit"))
)Basic Form
Create forms with action and method attributes.
form(attrs().action("/submit").method("POST"),
div(
label(attrs().for_("name"), text("Name")),
input(attrs().type("text").id("name").name("name")
.placeholder("Enter your name").required())
),
div(
label(attrs().for_("email"), text("Email")),
input(attrs().type("email").id("email").name("email")
.placeholder("you@example.com").required())
),
button(attrs().type("submit"), text("Submit"))
)Form Structure
Group form fields with fieldset and legend.
form(attrs().action("/register").method("POST"),
fieldset(
legend("Personal Information"),
div(
label(attrs().for_("fname"), text("First Name")),
input(attrs().type("text").id("fname").name("firstName"))
),
div(
label(attrs().for_("lname"), text("Last Name")),
input(attrs().type("text").id("lname").name("lastName"))
)
),
fieldset(
legend("Account"),
div(
label(attrs().for_("uname"), text("Username")),
input(attrs().type("text").id("uname").name("username"))
)
),
button(attrs().type("submit"), text("Register"))
)Input Types
All HTML5 input types are supported.
// Text inputs
input(attrs().type("text").name("name"))
input(attrs().type("email").name("email"))
input(attrs().type("password").name("password"))
input(attrs().type("tel").name("phone"))
input(attrs().type("url").name("website"))
input(attrs().type("search").name("query"))
// Number inputs
input(attrs().type("number").name("age").min("0").max("120"))
input(attrs().type("range").name("volume").min("0").max("100"))
// Date/time inputs
input(attrs().type("date").name("birthdate"))
input(attrs().type("time").name("appointment"))
input(attrs().type("datetime-local").name("meeting"))
// Other inputs
input(attrs().type("color").name("favorite"))
input(attrs().type("file").name("avatar").accept("image/*"))
input(attrs().type("hidden").name("userId").value("123"))Textarea
textarea(attrs()
.name("bio")
.rows(4)
.cols(50)
.placeholder("Tell us about yourself...")
.maxlength("500"))Form Input Builders
JWeb provides type-safe input builders with validation and styling built-in.
import static com.osmig.Jweb.framework.elements.Elements.*;
// Text inputs with labels
textInput("username", "Username")
textInput("fullName", "Full Name").placeholder("John Doe")
// Email with validation
emailInput("email", "Email Address").required()
// Password with constraints
passwordInput("password", "Password")
.minLength(8)
.required()
// Number inputs
numberInput("age", "Age").min(0).max(120)
numberInput("price", "Price").step(0.01)Complete Field Builder
The field() builder creates a full form field with label, input, and error container.
// Basic text field
field("name").label("Full Name").text().required()
// Email field with placeholder
field("email")
.label("Email Address")
.email()
.placeholder("user@example.com")
.required()
// Password field
field("password")
.label("Password")
.password()
.minLength(8)
.required()
// Number with range
field("quantity")
.label("Quantity")
.number()
.min(1)
.max(100)
.value(1)Checkbox and Radio
// Single checkbox
checkbox("terms", "I agree to the terms")
checkbox("newsletter", "Subscribe to newsletter").checked()
// Radio group
radioGroup("gender",
radio("male", "Male"),
radio("female", "Female"),
radio("other", "Other")
)
// Inline radio
radioGroup("size",
radio("s", "Small"),
radio("m", "Medium"),
radio("l", "Large")
).inline()Select Dropdown
// Simple select
selectField("country", "Country",
option("us", "United States"),
option("uk", "United Kingdom"),
option("ca", "Canada")
)
// With groups
selectField("car", "Choose a car",
optgroup("Swedish Cars",
option("volvo", "Volvo"),
option("saab", "Saab")
),
optgroup("German Cars",
option("mercedes", "Mercedes"),
option("audi", "Audi")
)
)
// Multi-select
selectField("skills", "Skills").multiple()
.options(skillsList)Input builders automatically generate IDs and wire up labels for accessibility.
Select Dropdowns
Create select elements with options.
// Basic select
select(attrs().name("country"),
option(attrs().value(""), text("Select a country")),
option(attrs().value("us"), text("United States")),
option(attrs().value("uk"), text("United Kingdom")),
option(attrs().value("ca"), text("Canada"))
)
// With selected option
select(attrs().name("status"),
option(attrs().value("active").selected(), text("Active")),
option(attrs().value("inactive"), text("Inactive"))
)
// Option groups
select(attrs().name("car"),
optgroup(attrs().label("Swedish Cars"),
option(attrs().value("volvo"), text("Volvo")),
option(attrs().value("saab"), text("Saab"))
),
optgroup(attrs().label("German Cars"),
option(attrs().value("mercedes"), text("Mercedes")),
option(attrs().value("audi"), text("Audi"))
)
)Dynamic Options
List<Country> countries = countryService.findAll();
select(attrs().name("country"),
option(attrs().value(""), text("Select...")),
each(countries, c ->
option(attrs().value(c.code()), text(c.name()))
)
)Checkboxes
Single and multiple checkboxes.
// Single checkbox
div(
input(attrs().type("checkbox").id("agree").name("agree").required()),
label(attrs().for_("agree"), text("I agree to the terms"))
)
// Checkbox group
fieldset(
legend("Select interests"),
div(
input(attrs().type("checkbox").id("tech").name("interests").value("tech")),
label(attrs().for_("tech"), text("Technology"))
),
div(
input(attrs().type("checkbox").id("sports").name("interests").value("sports")),
label(attrs().for_("sports"), text("Sports"))
),
div(
input(attrs().type("checkbox").id("music").name("interests").value("music")),
label(attrs().for_("music"), text("Music"))
)
)Radio Buttons
fieldset(
legend("Select plan"),
div(
input(attrs().type("radio").id("free").name("plan").value("free")),
label(attrs().for_("free"), text("Free"))
),
div(
input(attrs().type("radio").id("pro").name("plan").value("pro").checked()),
label(attrs().for_("pro"), text("Pro - $9/month"))
),
div(
input(attrs().type("radio").id("enterprise").name("plan").value("enterprise")),
label(attrs().for_("enterprise"), text("Enterprise - $99/month"))
)
)HTML5 Validation
Use built-in validation attributes.
// Required field
input(attrs().type("text").name("name").required())
// Email validation
input(attrs().type("email").name("email").required())
// Min/max length
input(attrs().type("text").name("username")
.minlength("3").maxlength("20"))
// Number range
input(attrs().type("number").name("age")
.min("18").max("100"))
// Pattern validation
input(attrs().type("text").name("phone")
.pattern("[0-9]{3}-[0-9]{3}-[0-9]{4}")
.title("Format: 123-456-7890"))Custom Error Messages
input(attrs()
.type("email")
.name("email")
.required()
.title("Please enter a valid email address"))
// Custom validation message via JavaScript
input(attrs()
.type("text")
.name("username")
.pattern("[a-z0-9_]+")
.data("error", "Username can only contain lowercase letters, numbers, and underscores"))Server-Side Validation
app.post("/register", req -> {
String email = req.body("email");
String password = req.body("password");
List<String> errors = new ArrayList<>();
if (email == null || !email.contains("@")) {
errors.add("Invalid email address");
}
if (password == null || password.length() < 8) {
errors.add("Password must be at least 8 characters");
}
if (!errors.isEmpty()) {
return registerForm(errors);
}
userService.register(email, password);
return Response.redirect("/login");
});Event Handlers
Handle form events with state.
State<String> searchTerm = useState("");
State<String> email = useState("");
// Input event (fires on every keystroke)
input(attrs()
.type("text")
.value(searchTerm.get())
.onInput(e -> searchTerm.set(e.value())))
// Change event (fires on blur)
input(attrs()
.type("email")
.value(email.get())
.onChange(e -> {
email.set(e.value());
validateEmail(e.value());
}))
// Focus and blur
input(attrs()
.type("text")
.onFocus(e -> showHint())
.onBlur(e -> hideHint()))Form Submit
State<String> name = useState("");
State<String> email = useState("");
State<Boolean> loading = useState(false);
form(attrs().onSubmit(e -> {
e.preventDefault();
loading.set(true);
// Submit data
submitForm(name.get(), email.get())
.thenRun(() -> loading.set(false));
}),
input(attrs().type("text").onInput(e -> name.set(e.value()))),
input(attrs().type("email").onInput(e -> email.set(e.value()))),
button(attrs().type("submit").disabled(loading.get()),
text(loading.get() ? "Submitting..." : "Submit"))
)File Upload
Handle file uploads with validation and storage.
import com.osmig.Jweb.framework.upload.FileUpload;
import com.osmig.Jweb.framework.upload.UploadedFile;
app.post("/upload", req -> {
UploadedFile file = FileUpload.getFile(req, "document");
if (file.isEmpty()) {
return Response.badRequest("No file provided");
}
Path saved = file.saveTo(Path.of("uploads"));
return Response.json(Map.of("path", saved.toString()));
});Upload Form
form(
action("/upload"),
method("post"),
enctype("multipart/form-data"),
input(type("file"), name("document")),
button(type("submit"), "Upload")
)File Properties
UploadedFile file = FileUpload.getFile(req, "document");
// Check if file was uploaded
if (file.isEmpty()) return error("No file");
// File info
String name = file.getOriginalFilename(); // "report.pdf"
String type = file.getContentType(); // "application/pdf"
long size = file.getSize(); // bytes
// Get content
byte[] bytes = file.getBytes();
InputStream stream = file.getInputStream();
// Type checks
if (file.isImage()) { /* PNG, JPG, GIF */ }
if (file.hasExtension("pdf", "doc")) { /* document */ }Validation
var validation = FileUpload.validate(file)
.required() // must be present
.maxSizeMB(10) // max 10MB
.imagesOnly(); // PNG, JPG, GIF only
if (!validation.isValid()) {
return Response.badRequest(validation.getFirstError());
}
// Or with specific extensions
FileUpload.validate(file)
.required()
.maxSizeMB(5)
.allowedExtensions("pdf", "doc", "docx")Multiple Files
// HTML
input(type("file"), name("images"), multiple(), accept("image/*"))
// Handler
List<UploadedFile> images = FileUpload.getFiles(req, "images");
for (UploadedFile image : images) {
var v = FileUpload.validate(image).maxSizeMB(5).imagesOnly();
if (v.isValid()) {
String filename = UUID.randomUUID() + "_" + image.getOriginalFilename();
image.saveAs(Path.of("uploads/gallery", filename));
}
}Save Options
// Save to directory (keeps original name)
file.saveTo(Path.of("uploads"));
// Save with custom name
file.saveAs(Path.of("uploads/custom-name.pdf"));
// Save with unique name
String unique = UUID.randomUUID() + "_" + file.getOriginalFilename();
file.saveAs(Path.of("uploads", unique));Configure max file size in application.yaml: spring.servlet.multipart.max-file-size