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 email

Builder 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
}
Use beforeRender for data loading, not render(). This keeps render() pure.