Awesome
HtmlFlow
HtmlFlow is a Java DSL to write typesafe HTML
in a fluent style, in both Java or Kotlin (for Kotlin check the examples)
You may use the utility Flowifier.fromHtml(String html)
if you need the HtmlFlow definition for an
existing HTML source:
HtmlFlow
.doc(System.out)
.html() // HtmlPage
.head()
.title().text("HtmlFlow").__()
.__() // head
.body()
.div().attrClass("container")
.h1().text("My first page with HtmlFlow").__()
.img().attrSrc("http://bit.ly/2MoHwrU").__()
.p().text("Typesafe is awesome! :-)").__()
.__() // div
.__() // body
.__(); // html
</td>
<td>
<html>
<head>
<title>HtmlFlow</title>
</head>
<body>
<div class="container">
<h1>My first page with HtmlFlow</h1>
<img src="http://bit.ly/2MoHwrU">
<p>Typesafe is awesome! :-)</p>
</div>
</body>
</html>
</td>
</tr>
<tr>
<td colspan="2" align="center">
↸  <code>Flowifier.fromHtml("...")</code>
</td>
</tr>
</table>
NOTICE for those migrating from legacy API <= 3.x read next about Migrating from HtmlFlow 3.x to 4.x.
HtmlFlow is the most performant engine among state of the art template engines like Velocity, Thymleaf, Mustache, etc and other DSL libraries for HTML such as j2Html and KotlinX Html. Check out the performance results in the most popular benchmarks at spring-comparing-template-engines and our fork of xmlet/template-benchmark.
xmlet/spring-petclinic provides an implementation of the Spring-based petclinic with HtmlFlow views.
Why another templating engine ?
Every general purpose language has its own template engine. Java has several. Most of the time, templates are defined in a new templating language (i.e. external DSL). To allow template engines to produce a view based on a template, they generally use the concept of model.
One of the problems of this technic is that you will end up with a template that won't be type checked. So if you have a typo inside your template, the compiler won't be able to help you before the template is rendered.
HtmlFlow took a different approach. Templates are expressed in an internal DSL. You will write normal Java code to produce your template. So, the full Java toolchain is at your disposal for templating. Put it simply, HtmlFlow templates are essentially plain Java functions.
HtmlFlow is not the only one using this approach. But it's the fastest one. Bonus points it also produces only valid HTML according to HTML 5.2.
Table of Contents
- Installation
- Core Concepts
- Data Binding
- If/else
- Loops
- Binding to Asynchronous data models
- Layout and partial views (aka fragments)
- Legacy API 3.x
- Changelog
- References
- License
- About
Installation
First, in order to include it to your Gradle project, simply add the following dependency, or use any other form provided in Maven Central Repository:
implementation 'com.github.xmlet:htmlflow:4.6'
You can also download the artifact directly from Maven Central Repository
Core Concepts
HtmlFlow builders:
- element builders (such as
body()
,div()
,p()
, etc) return the created element text()
andraw()
return the parent element (e.g..h1().text("...")
returns theH1
parent)..text()
escapes HTML, whileraw()
doesn't.- attribute builders -
attr<attribute name>()
- return their parent (e.g..img().attrSrc("...")
returns theImg
). __()
orl
in Kotlin, returns the parent element and emits the end tag of an element.
HtmlFlow provides both an eager and a lazy approach for building HTML.
This allows the Appendable
to be provided either beforehand or later when
the view is rendered.
The doc()
and view()
factory methods follow each of these approaches:
- eager:
HtmlFlow.doc(System.out).html().body().h1().text("Welcome").__().table().tr()...
- eager in Kotlin:
System.out.doc { html { body { h1.text("Welcome").l.table { tr {...} } } } }
- lazy:
var view = HtmlFlow.<Model>view(page -> page.html().body().text("Welcome").__().table().tr()...)
- lazy in Kotlin:
val view = view<Model> { html { body { h1.text("Welcome").l.table { tr {...} } } } }
An HtmlView
is more performant than an HtmlDoc
when we need to bind
the same template with different data models.
In this scenario, static HTML blocks are resolved only once, on HtmlView
instantiation.
Given an HtmlView
instance, e.g. view
, we can render the HTML using one of the
following approaches:
String html = view.render(tracks)
view.setOut(System.out).write(tracks)
The setOut()
method accepts any kind of Appendable
object.
In the following examples, we showcase using only HtmlDoc
.
You can find the equivalent HtmlView
definition on htmlflow.org
Data Binding
Web templates in HtmlFlow are defined using functions (or methods in Java). The
model (or context object) may be passed as arguments to such functions.
Next, we have an example of a dynamic web page binding to a Track
object.
void trackDoc(Appendable out, Track track) {
HtmlFlow.doc(out)
.html()
.body()
.ul()
.li()
.of((li) -> li
.text(format("Artist: %s", track.getArtist())))
.__() // li
.li()
.of((li) -> li
.text(format("Track: %s", track.getName())))
.__() // li
.__() // ul
.__() // body
.__(); // html
}
...
trackDoc(System.out, new Track("David Bowie", "Space Oddity"));
trackView equivalent definition to trackDoc
.
The of()
and dynamic()
builders in HtmlDoc
and HtmlView
, respectively,
are utilized to chain Java code in the definition of web templates:
of(Consumer<E> cons)
returns the same elementE
, whereE
is the parent HTML element.dynamic(BiConsumer<E, M> cons)
- similar to.of()
but the consumer receives an additional argumentM
(model).
If/else
Regarding the previous template of trackDoc
or trackView
, consider, for
example, that you would like to display the year of the artist's death for cases
where the artist has already passed away.
Considering that Track
has a property diedDate
of type LocalDate
, we can interleave
the following HtmlFlow snippet within the ul
to achieve this purpose:
void trackDoc(Appendable out, Track track) {
...
.ul()
...
.of(ul -> {
if(track.getDiedDate() != null)
ul.li().text(format("Died in %d", track.getDiedDate().getYear())).__();
})
...
}
trackView equivalent definition to trackDoc
.
Loops
You can utilize any Java loop statement in your web template definition. Next,
we present an example that takes advantage of the forEach
loop method of
Iterable
:
void playlistDoc(Appendable out, List<Track> tracks) {
HtmlFlow.doc(out)
.html()
.body()
.table()
.tr()
.th().text("Artist").__()
.th().text("Track").__()
.__() // tr
.of(table -> tracks.forEach( trk ->
table
.tr()
.td().text(trk.getArtist()).__()
.td().text(trk.getName()).__()
.__() // tr
))
.__() // table
.__() // body
.__(); // html
}
playlistView equivalent definition to playlistDoc
.
Binding to Asynchronous data models
To ensure well-formed HTML, HtmlFlow needs to observe the completion of asynchronous models. Otherwise, text or HTML elements following an asynchronous model binding may be emitted before the HTML resulting from the asynchronous model.
To bind an asynchronous model, one should use the builder
.await(parent, model, onCompletion) -> ...)
where the onCompletion
callback signals to HtmlFlow that it can proceed to the
next continuation.
Next we present the asynchronous version of the playlist web template.
Instead of a List<Track>
we are binding to a Flux,
which is a Reactive Streams Publisher
with reactive operators that emits 0 to N elements.
HtmlViewAsync<Flux<Track>> playlistView = HtmlFlow.viewAsync(view -> view
.html()
.body()
.table()
.tr()
.th().text("Artist").__()
.th().text("Track").__()
.__() // tr
.<Flux<Track>>await((table, tracks, onCompletion) -> tracks
.doOnComplete(onCompletion::finish)
.doOnNext( trk ->
table
.tr()
.td().text(trk.getArtist()).__()
.td().text(trk.getName()).__()
.__()
))
.__() // table
.__() // body
.__() // html
);
HtmlFlow await feature works regardless the type of asynchronous model and can be used with any kind of asynchronous API.
References
- 2024, Progressive Server-Side Rendering with Suspendable Web Templates, 25th edition of WISE, Doha, Qatar, (slides).
- 2023, Enhancing SSR in Low-Thread Web Servers, 19th WebIst conference, 2013, Rome - This paper highlights the HtmlFlow templating approach that embraces any asynchronous AP I (e.g., Publisher, promises, suspend functions, flow, etc.) and allows for multiple asynchronous data sources (slides).
- 2020, Text Web Templates Considered Harmful, Part of the Lecture Notes in Business Information Processing book series (LNBIP, volume 399). This paper shows how a DSL for HTML (such as HtmlFlow or Kotlinx.Html) provides unopinionated web templates with boundless resolving features only ruled by the host programming language (such as Java, Kotlin or JavaScript).
- 2019, HoT: Unleash Web Views with Higher-order Templates, 15th WebIst conference, 2019, Viena - This paper highlights the compositional nature of HtmlFlow to compose templates through higher-order functions.
- 2018, Domain Specific Language generation based on a XML Schema. Slides of the MsC thesis presentation of Luís Duarte.
- 2018, Modern Type-Safe Template Engines - You can find more details in this DZone article about performance comparison.
License
About
HtmlFlow was created by Miguel Gamboa (aka fmcarvalho), an assistant professor of Computer Science and Engineering of ISEL, Polytechnic Institute of Lisbon.