View helper
After CremaServiceProvider boots, Kona binds the app’s CremaEngine to a global factory. Controllers and routes use that factory instead of resolving the engine by hand.
- Kotlin: top-level
view("name")/view("name", data)→View - Java:
Views.make("name")/Views.make("name", data)→View - Either:
Views.response(...),Crema.view(...), orview(...).toResponse()→ HTMLResponse
This page is the full public surface for that factory. Template syntax lives in Rendering & data and the other Crema guides.
Where it sits in the request
Section titled “Where it sits in the request”CremaServiceProvider.registerbuilds aCremaEngine(file finder on the views root) and puts it in the container.CremaServiceProvider.bootcallsViews.setEngine(engine).- A controller or route handler calls
view(...)/Views.make(...). - You return
toResponse()(orViews.response/Crema.view) as the HTTP Response. Handlers today returnResponse, not a bareView.
If you call view before boot (or without registering Crema), you get IllegalStateException: View engine not configured.
Minimal controller
Section titled “Minimal controller”import org.konaframework.crema.viewimport org.konaframework.http.Requestimport org.konaframework.http.Response
class WelcomeController { fun index(request: Request): Response = view("welcome", mapOf("name" to "Kona")).toResponse()}import org.konaframework.crema.Views;import org.konaframework.http.Request;import org.konaframework.http.Response;import java.util.Map;
public final class WelcomeController { public Response index(Request request) { return Views.make("welcome", Map.of("name", "Kona")).toResponse(); }}Route handlers work the same way:
router.get("/") { view("welcome", mapOf("name" to "Kona")).toResponse() }router.get("/", request -> Views.make("welcome", Map.of("name", "Kona")).toResponse());API surface
Section titled “API surface”view / Views.make
Section titled “view / Views.make”| Call | Result |
|---|---|
view("home") / Views.make("home") |
View with empty data |
view("home", map) / Views.make("home", map) |
View with that data map copied |
View methods:
| Method | Behavior |
|---|---|
with(key, value) |
Set one key; returns the same View |
with(map) |
Merge many keys |
data() |
Snapshot of keys bound on this view (not later shares) |
name |
Canonical name (posts.show) |
render() |
HTML string (shared data merged in) |
toResponse(status = 200) |
Response.html(...) |
Views.render / Views.response / Crema.view
Section titled “Views.render / Views.response / Crema.view”| Call | Result |
|---|---|
Views.render(name, data?) |
HTML String |
Views.response(name, data?, status?) |
HTML Response |
Crema.view(name, data?, status?) |
Same as Views.response (bound engine) |
Crema.view(engine, name, data?, status?) |
Same, but uses an explicit CremaEngine (tests / custom finders) |
Existence and first match
Section titled “Existence and first match”| Call | Behavior |
|---|---|
Views.exists(name) |
true when the finder can load the template |
Views.first(names, data?) |
First resolvable name; else ViewNotFoundException listing candidates |
CremaEngine.first(...) |
Same on a local engine |
Use first when a theme or tenant may override a default template:
return Views.first( listOf("themes.$theme.home", "home"), mapOf("title" to "Hi"),).toResponse()return Views.first( List.of("themes." + theme + ".home", "home"), Map.of("title", "Hi")).toResponse();Shared data
Section titled “Shared data”Share keys once (service provider, middleware, or boot code). Every later make / render merges them; per-view data wins.
| Call | Behavior |
|---|---|
Views.share(key, value) |
One key |
Views.share(map) |
Many keys |
Views.shared() |
Copy of current shared map |
Views.flushShared() |
Clear all shared keys |
CremaEngine.share / shared / flushShared |
Same on a local engine |
Views.share("appName", "Kona")Views.share(mapOf("locale" to "en"))
// every view sees appName / locale unless overriddenview("dashboard", mapOf("user" to user)).toResponse()Views.share("appName", "Kona");Views.share(Map.of("locale", "en"));
Views.make("dashboard", Map.of("user", user)).toResponse();For CremaContext-based renders (engine.render(name, context)), shared keys fill missing context keys only; keys already present on the context are left alone. Sections and stacks on that context are preserved.
Factory wiring (tests)
Section titled “Factory wiring (tests)”| Call | Behavior |
|---|---|
Views.setEngine(engine) |
Bind (also done by CremaServiceProvider.boot) |
Views.clearEngine() |
Unbind (tests) |
Views.engine() |
Bound engine, or throws if none |
Views.configured() |
Whether an engine is bound |
View names: dots and slashes
Section titled “View names: dots and slashes”Names are normalized with normalizeViewName: slashes become dots, and stray leading/trailing separators are trimmed.
| You write | Canonical | File (under views root) |
|---|---|---|
home |
home |
home.crema.html |
posts.show |
posts.show |
posts/show.crema.html |
posts/show |
posts.show |
posts/show.crema.html |
Both Views.exists("posts.show") and Views.exists("posts/show") are true for the same file. In-memory MapViewFinder.put("posts/show", …) stores under posts.show as well.
Returning from controllers and routes
Section titled “Returning from controllers and routes”Controllers and Handlers return Response. Convert a view in one of these ways:
view("posts.show", mapOf("post" to post)).toResponse()view("posts.show", mapOf("post" to post)).toResponse(201)Views.response("posts.show", mapOf("post" to post))Crema.view("posts.show", mapOf("post" to post), 200)Views.make("posts.show", Map.of("post", post)).toResponse();Views.make("posts.show", Map.of("post", post)).toResponse(201);Views.response("posts.show", Map.of("post", post));Crema.view("posts.show", Map.of("post", post), 200);Accumulate data before responding:
return view("checkout") .with("cart", cart) .with(mapOf("tax" to tax, "total" to total)) .toResponse()Pair with Controllers, Routing, and Request & response.
When to resolve CremaEngine yourself
Section titled “When to resolve CremaEngine yourself”Prefer view / Views in application code. Resolve or construct CremaEngine when you need:
- A custom
ViewFinder(tests withMapViewFinder, alternate roots) - Directives / tags registered only on that engine (
directive,blockDirective,tag) renderSourcefor inline templatesCrema.view(engine, …)without touching the global factory
val engine = CremaEngine(MapViewFinder().put("x", "Hi {{ name }}"))assertEquals("Hi Ada", engine.render("x", mapOf("name" to "Ada")))Pitfalls
Section titled “Pitfalls”| Pitfall | What to do |
|---|---|
View engine not configured |
Register CremaServiceProvider and app.boot() before calling view / Views |
Returning a bare View from a handler |
Call toResponse() — the kernel expects Response |
Shared data “not updating” an already-built View |
Shared keys merge at render time via the engine; View.data() only shows keys you set with with / make |
| Per-view key ignored | A later with / data map entry overrides shared; check for typos |
ViewNotFoundException |
Confirm the file is under the views root with .crema.html or .crema.kt, and the name matches (posts.show ↔ posts/show.crema.html) |
| Slash vs dot mismatch in tests | Prefer dotted names in MapViewFinder.put, or either form after normalization |
Calling Views from a unit test without binding |
Views.setEngine(CremaEngine(...)) in setup; Views.clearEngine() in teardown |
Related
Section titled “Related”- Getting started with Crema — templates, paths, scaffold
- Rendering & data — echoes, expressions,
@json,@lang - Controllers — returning responses from actions
- Routing — wiring handlers
- Request & response —
Response.htmland friends