Components
Components are reusable UI pieces that implement the Template interface. They encapsulate structure, styling, and behavior into self-contained units.
Overview
Create a component by implementing Template and its render() method. Pass data via constructor parameters.
public class MyComponent implements Template {
private final String prop;
public MyComponent(String prop) {
this.prop = prop;
}
public Element render() {
return div(text(prop));
}
}
// Usage: new MyComponent("Hello")Basic Component
Components implement the Template interface.
public class Card implements Template {
private final String title;
private final String content;
public Card(String title, String content) {
this.title = title;
this.content = content;
}
public Element render() {
return div(attrs().class_("card"),
h3(title),
p(content)
);
}
}
// Usage
new Card("Welcome", "Hello, world!")Using Components
Components are used like any other element.
div(
h1("Dashboard"),
new Card("Users", "Manage your users"),
new Card("Settings", "Configure the app"),
new Card("Reports", "View analytics")
)
// In a list
List<Product> products = productService.findAll();
div(each(products, p ->
new ProductCard(p)
))Props via Constructor
Pass data to components through constructor parameters.
public class UserCard implements Template {
private final User user;
private final boolean showEmail;
public UserCard(User user) {
this(user, true);
}
public UserCard(User user, boolean showEmail) {
this.user = user;
this.showEmail = showEmail;
}
public Element render() {
return div(attrs().class_("user-card"),
img(attrs().src(user.getAvatar()).alt(user.getName())),
h3(user.getName()),
when(showEmail, () -> p(user.getEmail())),
when(user.isAdmin(), () -> badge("Admin"))
);
}
}
// Usage
new UserCard(user)
new UserCard(user, false) // Hide emailBuilder Pattern
For components with many optional props.
public class Button implements Template {
private final String text;
private String variant = "primary";
private String size = "medium";
private boolean disabled = false;
public Button(String text) { this.text = text; }
public Button variant(String v) { variant = v; return this; }
public Button size(String s) { size = s; return this; }
public Button disabled() { disabled = true; return this; }
public Element render() {
return button(attrs()
.class_("btn btn-" + variant + " btn-" + size)
.disabled(disabled),
text(text));
}
}
// Usage
new Button("Submit")
new Button("Cancel").variant("secondary")
new Button("Save").size("large").disabled()Components with Children
Accept child elements via constructor.
public class Panel implements Template {
private final String title;
private final Element[] children;
public Panel(String title, Element... children) {
this.title = title;
this.children = children;
}
public Element render() {
return div(attrs().class_("panel"),
div(attrs().class_("panel-header"), h3(title)),
div(attrs().class_("panel-body"), fragment(children))
);
}
}
// Usage
new Panel("User Info",
p("Name: John Doe"),
p("Email: john@example.com"),
button("Edit Profile")
)Slot Pattern
Named slots for complex layouts.
public class Dialog implements Template {
private final Element header;
private final Element body;
private final Element footer;
public Dialog(Element header, Element body, Element footer) {
this.header = header;
this.body = body;
this.footer = footer;
}
public Element render() {
return div(attrs().class_("dialog"),
div(attrs().class_("dialog-header"), header),
div(attrs().class_("dialog-body"), body),
div(attrs().class_("dialog-footer"), footer)
);
}
}
// Usage
new Dialog(
h2("Confirm Delete"),
p("Are you sure you want to delete this item?"),
fragment(
button("Cancel"),
button(attrs().class_("btn-danger"), text("Delete"))
)
)Component Composition
Build complex UIs by composing smaller components.
// Small, focused components
public class Avatar implements Template {
private final String src, alt;
public Avatar(String src, String alt) { this.src = src; this.alt = alt; }
public Element render() {
return img(attrs().src(src).alt(alt).class_("avatar"));
}
}
public class UserName implements Template {
private final String name;
public UserName(String name) { this.name = name; }
public Element render() {
return span(attrs().class_("username"), text(name));
}
}
// Composed component
public class UserBadge implements Template {
private final User user;
public UserBadge(User user) { this.user = user; }
public Element render() {
return div(attrs().class_("user-badge"),
new Avatar(user.getAvatar(), user.getName()),
new UserName(user.getName())
);
}
}Component Reuse
// Reusable across the app
header(
new Logo(),
new Navigation(),
new UserBadge(currentUser)
)
// In a list
div(each(comments, c ->
div(
new UserBadge(c.getAuthor()),
p(c.getText()),
new Timestamp(c.getCreatedAt())
)
))Lifecycle Hooks
Templates support lifecycle hooks for data loading, cleanup, and page metadata.
beforeRender & afterRender
Server-side hooks for data loading and cleanup.
public class UserPage implements Template {
private final UserService userService;
private User user;
public UserPage(UserService userService) {
this.userService = userService;
}
@Override
public void beforeRender(Request request) {
int userId = request.paramInt("id");
this.user = userService.findById(userId);
}
@Override
public void afterRender(Request request) {
// Cleanup, logging, analytics
}
@Override
public Element render() {
return div(h1(user.getName()));
}
}Page Title & Meta
Dynamic page title and SEO metadata.
@Override
public Optional<String> pageTitle() {
return Optional.of(product.getName() + " | Store");
}
@Override
public Optional<String> metaDescription() {
return Optional.of(product.getDescription().substring(0, 150));
}Extra Head Elements
Add custom elements to the HTML head.
@Override
public Optional<Element> extraHead() {
return Optional.of(fragment(
meta(name("og:title"), content(getTitle())),
meta(name("og:image"), content(getImageUrl())),
link(rel("preconnect"), href("https://fonts.googleapis.com")),
link(rel("stylesheet"), href("/css/page.css"))
));
}Client-Side Lifecycle
JavaScript hooks for DOM ready and cleanup.
@Override
public String onMount() {
return "initCharts(); setupWebSocket();";
}
@Override
public String onUnmount() {
return "closeWebSocket(); saveScrollPosition();";
}Caching
Control response caching for performance.
@Override
public boolean cacheable() {
return currentUser == null; // Only cache for anonymous users
}
@Override
public int cacheDuration() {
return 3600; // Cache for 1 hour
}