Conditionals
JWeb provides fluent conditional rendering utilities. Control what renders based on conditions, iterate over collections, and handle multiple cases cleanly.
Overview
Two shapes: when(condition, element) for optional content and when(condition, ifTrue, ifFalse) for a choice — the same call, one argument longer. When a branch is long enough that the argument list stops reading, when(condition).then(...).otherwise(...) says the same thing down the page. Both branches take an Element, a lambda, or a String; each() iterates.
boolean condition = true;
Element element() { return div("content"); }
Element trueElement() { return div("yes"); }
Element falseElement() { return div("no"); }
List<String> list = List.of("a", "b", "c");
Element renderItem(String item) { return li(item); }
// Show if true
when(condition, element())
// Either/or — one argument longer
when(condition, trueElement(), falseElement())
// The same choice, chained, when a branch is long
when(condition)
.then(trueElement())
.otherwise(falseElement())
// Iterate
each(list, item -> renderItem(item))when() - Conditional Display
Show elements only when a condition is true.
class CurrentUser {
String getAvatar() { return "/avatar.png"; }
String getName() { return "Ada"; }
}
class Cart {
boolean hasItems() { return true; }
int getItemCount() { return 3; }
}
boolean isLoggedIn = true;
CurrentUser user = new CurrentUser();
Cart cart = new Cart();
// Basic when
when(isLoggedIn, () -> span("Welcome back!"))
// With complex element
when(user != null, () ->
div(
img(src(user.getAvatar())),
span(user.getName())
)
)
// Check property
when(cart.hasItems(), () ->
div(
span("Items: " + cart.getItemCount()),
button("Checkout")
)
)Inverse Conditions
A one-sided when() takes a negated condition; two branches are one call, so the predicate is never written twice.
boolean isLoggedIn = true;
List<String> items = List.of("Apple", "Banana");
Element emptyState() { return div("No items"); }
Element itemList(List<String> list) { return ul(each(list, item -> li(item))); }
Element userMenu() { return div("Menu"); }
Element loginButton() { return button("Log in"); }
// Show login link when NOT logged in
when(!isLoggedIn, a(href("/login"), "Please log in"))
// Show the empty state instead of the list — one call, one predicate
when(items.isEmpty(), emptyState(), itemList(items))
// Same for a menu
when(isLoggedIn, userMenu(), loginButton())Conditional Text and Classes
A String branch is text, so when() also composes class names: classes(...) joins its parts and skips the ones that did not match.
int unread = 3;
boolean active = true;
String tag = "java";
String url = "/tag/java";
// Text
span(when(unread > 0, unread + " new", "All caught up"))
// Class names — no string concatenation, no ternary
a(classes("chip", when(active, "chip-on")), href("/tag/" + tag), tag)
// The same on an attrs() chain
a(attrs().cls("chip").classIf(active, "chip-on").href(url), tag)Either/Or
when(condition, ifTrue, ifFalse) is the choice: the same call as the one-sided form with one more argument. Each branch may be an Element, a lambda (only the taken branch is built), or a String.
boolean isAdmin = true;
Element adminDashboard() { return div("Admin dashboard"); }
Element userDashboard() { return div("User dashboard"); }
class CurrentUser { boolean isActive() { return true; } }
CurrentUser user = new CurrentUser();
boolean isExpanded = false;
boolean isPremium = true;
Element premiumContent() { return div("Premium content"); }
Element guestContent() { return div("Guest content"); }
// condition ? ifTrue : ifFalse
when(isAdmin, adminDashboard(), userDashboard())
// Status display
when(user.isActive(),
span(cls("text-green"), "Active"),
span(cls("text-red"), "Inactive"))
// Toggle button text — a String branch is text
button(when(isExpanded, "Show Less", "Show More"))
// Lambdas when a branch is expensive to build
when(isPremium, () -> premiumContent(), () -> guestContent())The Chained Form
When a branch runs several lines, the chain puts the two outcomes under each other instead of inside an argument list. A chain left without otherwise() is still an element — it renders the matched branch, or nothing.
boolean active = true;
String name = "java";
boolean isPremium = true;
Element premiumContent() { return div("Premium content"); }
boolean isRegistered = true;
Element basicContent() { return div("Basic content"); }
Element guestContent() { return div("Guest content"); }
when(active)
.then(span(cls("chip chip-on"), name))
.otherwise(a(href("/tag/" + name), cls("chip"), name))
// Nested choices read better as a chain than as nested arguments
when(isPremium)
.then(premiumContent())
.otherwise(when(isRegistered, basicContent(), guestContent()))Java's Ternary Still Works
For a value rather than an element — a colour token, a number — the ternary is shorter and stays in the language.
boolean isError = false;
CSSValue RED = hex("#dc2626");
CSSValue GREEN = hex("#16a34a");
String message = "Saved";
// A style value
div(style().color(isError ? RED : GREEN), message)
// A class token
div(cls(isError ? "error" : "success"), message)Multi-Way Choices
when() covers one branch or two. Branching on a value is what Java's switch expression is for, so there is no elif chain and no match() — both are gone in 3.0.
enum Role { ADMIN, MODERATOR, EDITOR, USER }
Role role = Role.ADMIN;
String status = "success";
int code = 200;
Element adminPanel() { return div("Admin"); }
Element moderatorPanel() { return div("Moderator"); }
Element editorPanel() { return div("Editor"); }
Element userPanel() { return div("User"); }
Element guestPanel() { return div("Guest"); }
Element greenBadge(String s) { return div(s); }
Element yellowBadge(String s) { return div(s); }
Element redBadge(String s) { return div(s); }
Element grayBadge(String s) { return div(s); }
Element serverError() { return div("500"); }
Element clientError() { return div("400"); }
Element redirect() { return div("300"); }
Element success() { return div("200"); }
Element unknown() { return div("?"); }
// Role-based content
Element panel = switch (role) {
case ADMIN -> adminPanel();
case MODERATOR -> moderatorPanel();
case EDITOR -> editorPanel();
case USER -> userPanel();
default -> guestPanel();
};
// Status badge
Element badge = switch (status) {
case "active" -> greenBadge("Active");
case "pending" -> yellowBadge("Pending");
case "suspended" -> redBadge("Suspended");
case "archived" -> grayBadge("Archived");
default -> grayBadge("Unknown");
};
// Ranges aren't a switch — a ternary chain (or a few when()s) reads fine
Element result = code >= 500 ? serverError()
: code >= 400 ? clientError()
: code >= 300 ? redirect()
: code >= 200 ? success()
: unknown();each() - List Iteration
Render elements for each item in a collection.
class User {
String getName() { return "Ada"; }
String getEmail() { return "ada@example.com"; }
boolean isAdmin() { return true; }
}
class UserService { List<User> findAll() { return List.of(new User()); } }
UserService userService = new UserService();
// Simple list
List<String> items = List.of("Apple", "Banana", "Cherry");
ul(each(items, item -> li(item)))
// Complex items
List<User> users = userService.findAll();
div(each(users, user ->
div(class_("user-card"),
h3(user.getName()),
p(user.getEmail()),
when(user.isAdmin(), () -> UI.badge("Admin"))
)
))Iterating With an Index
Iterate over an index range to access positions.
class User {
String getName() { return "Ada"; }
String getEmail() { return "ada@example.com"; }
}
List<String> items = List.of("Apple", "Banana", "Cherry");
List<User> users = List.of(new User());
// Numbered list
ol(each(IntStream.range(0, items.size()).boxed().toList(), i ->
li((i + 1) + ". " + items.get(i))
))
// Alternating rows
table(tbody(each(IntStream.range(0, users.size()).boxed().toList(), i ->
tr(class_(i % 2 == 0 ? "even" : "odd"),
td(users.get(i).getName()),
td(users.get(i).getEmail())
)
)))
// Separator between items
div(each(IntStream.range(0, items.size()).boxed().toList(), i ->
span(items.get(i), i < items.size() - 1 ? ", " : "")
))Empty State
List<String> items = List.of("Apple", "Banana", "Cherry");
// Show empty state when list is empty
items.isEmpty()
? div(class_("empty"), "No items found")
: ul(each(items, item -> li(item)))