Integrations
data-*
DataStar is a hypermedia framework that builds interactive web
applications by extending HTML with declarative data-* attributes. Rather than introducing a
template language or a client-side component model, it enhances standard HTML with reactive
behavior, making it a natural fit for HtmlFlow.
The htmlflow-datastar module extends htmlflow-kotlin with a type-safe DSL for DataStar's
attributes and expressions. Instead of writing data-* attributes and JavaScript expressions as
strings, they are represented through Kotlin APIs that provide compile-time validation, IDE
completion, and seamless integration with the existing HtmlFlow Kotlin DSL.
1. Dependencies
1. Dependency
Add the htmlflow-datastar module to your project.
implementation 'com.github.xmlet:htmlflow-datastar:1.3.0-alpha'
2. Type-safe signals and attributes
DataStar manages reactive state through signals, which are created and consumed by different data-* attributes. In HtmlFlow Datastar, a signal
is created once using dataSignal and represented as a typed Signal<T> object that can be reused throughout the view. Instead of referencing signals
through string-based data-* attributes, builders such as dataText, dataShow, and dataClass accept Signal<T> objects directly, providing
type safety, IDE completion, and consistent signal references. As shown below, the count signal is created once and then passed to dataText to display its value.
div {
val count: Signal<Int> = dataSignal("count", 0)
div {
span {
attrId("counter")
dataText { +count }
}
}
}
This produces the equivalent DataStar markup:
<div data-signals:count="0">
<span id="counter" data-text="$count"></span>
</div>
Reusing the same Signal<T> object avoids misspelled signal names and keeps references consistent.
3. Type-safe events and modifiers
User interactions are defined through data-on:* attributes. HtmlFlow Datastar exposes these as type-safe
event builders, allowing the event type (such as Click or Input), the action, and any modifiers to be expressed
using Kotlin APIs instead of manually constructing strings.
In the following example, the input field is bound to the search signal. Every Input event triggers a GET request, but
only after the user has stopped typing for 200 ms, thanks to the debounce modifier. This reduces the number of requests sent
while the user is typing and improves the overall user experience.
Notice that the DataStar get action accepts a Kotlin function reference. During rendering, this reference is resolved
to the URL declared in the function's route annotation, eliminating the need to hardcode endpoint paths and ensuring they remain
synchronized with the server-side route definitions.
div {
val search: Signal<String> = dataSignal("search", "")
input {
attrType(EnumTypeInputType.TEXT)
attrPlaceholder("Search...")
dataBind(search)
dataOn(Input) {
get(::search)
modifiers { debounce(200.milliseconds) }
}
}
}
Which produces:
<div data-signals:search="''">
<input type="text" placeholder="Search..." data-bind:search data-on:input__debounce="@get('/examples/search')" />
</div>
Typed events and modifiers eliminate invalid event names and malformed modifier syntax.
4. Type-safe expressions
Many DataStar attributes accept JavaScript-like expressions. Instead of writing these expressions as
strings, HtmlFlow Datastar represents them as Kotlin objects that can be combined using familiar
operators such as and, or, and not.
In the following example, dataIndicator creates a signal named _fetching. The leading _
follows a DataStar convention indicating that the signal is local and is therefore not sent to the server
when an HTTP request is made. The signal's value is automatically set to true while a request is in
progress and back to false when the request completes.
The dataAttr attribute binds the value of any HTML attribute to an expression, keeping it automatically
synchronized with the expression's result. In this case, binding the button's disabled attribute to _fetching
ensures that the button is disabled while the request is in progress.
The same signal is also used in the dataOn expression to prevent multiple requests from being sent if the user
clicks the button before the browser has applied the disabled attribute. This provides an additional safeguard against duplicate requests.
button {
val fetching: Signal<Boolean> = dataIndicator("_fetching")
dataAttr("disabled") { +fetching }
dataOn(Click) {
!fetching and get(::clickToLoadMore)
}
text("Load More")
}
Which generates:
<button
data-indicator:_fetching
data-attr:disabled="$_fetching"
data-on:click="!$_fetching && @get('/examples/click_to_load/more')"
>
Load More
</button>
Expressions can be composed and refactored using Kotlin operators instead of string concatenation.
HtmlFlow Datastar generates standard DataStar HTML, enabling developers to build reactive user interfaces declaratively with a fully type-safe Kotlin DSL while preserving compatibility with the DataStar runtime.
http4k
http4k is a Kotlin HTTP toolkit built on the idea of handlers as plain
functions ((Request) -> Response). HtmlFlow plugs into http4k's templating SPI through the
htmlflow-view-loader module (com.github.xmlet:htmlflow-view-loader), so HtmlFlow views can
be used wherever http4k expects a template engine: alongside, or instead of, Pebble, Thymeleaf,
or Handlebars.
The view loader does convention-based resolution: you expose your views as
HtmlView<MyViewModel> members (a val property or a function), point the loader at a package, and
it builds a registry that maps each model type to its view (matching superclasses and interfaces as
a fallback). The result is a (ViewModel) -> String function, which is exactly http4k's
TemplateRenderer.
1. Dependencies
Add the Kotlin DSL, the view loader, and http4k's template core:
implementation 'com.github.xmlet:htmlflow-kotlin:5.0.4'
implementation 'com.github.xmlet:htmlflow-view-loader:5.0.4'
implementation 'org.http4k:http4k-template-core'
2. A view model and its view
The model implements http4k's ViewModel; the view is an HtmlView typed by that model. To find a
view, the loader scans each class in the package for a zero-argument member that returns an
HtmlView — a val property or a function both work. It just needs an instance to read that member
from, and it tries several ways to get one: a Kotlin object, a top-level (file-level) declaration,
a class with a no-argument constructor, a companion object, a getInstance() singleton, or a
constructor that takes an HtmlFlow.ViewFactory. A Kotlin object is the simplest, so this example
uses one:
import htmlflow.*
import org.http4k.template.ViewModel
import org.xmlet.htmlapifaster.body
import org.xmlet.htmlapifaster.h1
data class Greeting(val name: String) : ViewModel
object Views {
val greeting: HtmlView<Greeting> = HtmlFlow.view {
it.html {
body {
dyn { model: Greeting -> h1 { text("Hello, ${model.name}!") } }
}
}
}
}
3. Build the renderer
ClasspathLoader(ViewModel::class.java).createRenderer(package) scans the package and returns a
renderer that resolves the right view from the model's type at call time:
import htmlflow.viewloader.ClasspathLoader
import org.http4k.template.TemplateRenderer
val renderer: TemplateRenderer =
ClasspathLoader(ViewModel::class.java).createRenderer("com.example.views")
4. Wire it into an http4k handler
Use http4k's viewModel body lens to render a model straight into a response. No explicit view
lookup is needed. The loader picks Views.greeting because the model is a Greeting:
import org.http4k.core.*
import org.http4k.core.ContentType.Companion.TEXT_HTML
import org.http4k.template.viewModel
val htmlView = Body.viewModel(renderer, TEXT_HTML).toLens()
val app: HttpHandler = { _: Request ->
Response(Status.OK).with(htmlView of Greeting("World"))
}
Set hotReload = true on createRenderer(...) during development to re-scan views on each render
(this also disables pre-encoding); keep it false (the default) in production so views are
pre-encoded once and cached. See Core Concepts → Configuring Views.