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)))