HTMX fragments & forms
A coherent working example of the framework's rendering and integration contracts.
SlotKey · Slot · RenderContext · Template
Submit to replace the actual rendered values. The template is shared; request data is not.
Initial title
Supply your own request values.
public static String typedPreview() {
SlotKey<String> title = SlotKey.of("example.title");
SlotKey<String> body = SlotKey.of("example.body");
Template card = new Div().withId("template-result")
.withChild(new HtmlTag("h3").withChild(Slot.of(title)))
.withChild(new Paragraph().withChild(Slot.of(body))).compile();
Form form = Form.create().withAction("/demos/api/template-card").withMethod(Form.Method.POST)
.withHxPost("/demos/api/template-card").withHxTarget("#template-result").withHxSwap("outerHTML")
.addField("Title", TextInput.create("title").withValue("Request-time title").required())
.addField("Body", TextInput.create("body").withValue("A different context, the same compiled structure.").required())
.withChild(Button.submit("Render with context"));
return new Div().withChild(form).withChild(new RawHtml(card.render(RenderContext.builder()
.with(title, "Initial title").with(body, "Supply your own request values.").build()))).render();
}
// POST /demos/api/template-card renders that same shared template with submitted title/body.
// The application adds CSRF fields and headers to forms before HTTP output.
Module structure & slot entries
A module builds its structure once. Live entries are escaped at render time; compiled entries accept only application-trusted rendered HTML. Both contexts reuse the same compiled module.
LiveEntry
Live <text>
CompiledEntry
Trusted compiled text
public static String moduleEntries() {
SlotKey<String> value = SlotKey.of("module.value");
io.mindspice.simplypages.core.Module module = new io.mindspice.simplypages.core.Module("section") {
protected void buildContent() {
withChild(new Paragraph().withChild(Slot.of(value)));
}
};
Template template = module.compile();
RenderContext live = RenderContext.builder().with(value, "Live <text>").build();
RenderContext compiled = RenderContext.builder().build()
.putCompiled(value, new HtmlTag("strong").withInnerText("Trusted compiled text").render());
SlotEntry liveEntry = live.getEntries().get(value);
SlotEntry compiledEntry = compiled.getEntries().get(value);
return new Div().withChild(new Paragraph(liveEntry.getClass().getSimpleName()))
.withChild(new RawHtml(template.render(live)))
.withChild(new Paragraph(compiledEntry.getClass().getSimpleName()))
.withChild(new RawHtml(template.render(compiled))).render();
}
TemplateComponent: a bound context
This adapter intentionally ignores its parent's context. The bound value below wins over the parent's different value.
Bound value
SlotKey<String> bound = SlotKey.of("bound");
Template view = new Paragraph().withChild(Slot.of(bound)).compile();
TemplateComponent.of(view, RenderContext.builder().with(bound, "Bound value").build())
.render(RenderContext.builder().with(bound, "Parent value").build());Compilation & live delegates
Custom render(RenderContext) overrides remain live delegates. A compiled view still dispatches the supplied context; sharing requires the delegate itself to be safe.
This value arrived through a live delegate
SlotKey<String> title = SlotKey.of("example.title");
Component live = context -> new Paragraph(context.get(title).orElse("")).render(context);
Template compiled = live.compile();
compiled.render(RenderContext.builder().with(title, "This value arrived through a live delegate").build());Forms use native POST actions and HTMX outerHTML swaps. The application supplies one hidden CSRF token and literal inherited headers. The editing example demonstrates multi-target OOB page/modal updates, including protected DELETE, validation drafts and permission failures.
Try editing and OOB updates →