HtmlFlow
HtmlFlow copied to clipboard
HtmlFlow Java DSL to write typesafe HTML
HtmlFlow
HtmlFlow is a Java DSL to write typesafe HTML
documents in a fluent style.
Use one of its view()
factory methods to get started with HtmlFlow, such as the following sample,
which produces the HTML on the right side.
Use the utility Flowifier.toFlow(url)
to get the HtmlFlow definition from the
corresponding HTML source:
view.render()   ↴
|
|
|
|
↸  Flowifier.toFlow(url)
|
Finally 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.
Check the implementation of the sample Spring-based petclinic with HtmlFlow views xmlet/spring-petclinic. You can find different kinds of dynamic views there.
Why another templating engine ?
Every general purpose language has its own template engine. Java has several. Most of the time, template engines have templates that are defined in new external DSL. To allow them to produce a view based on the templates files, 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 tool chain 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 document according to HTML 5.2.
Table of Contents
- Installation
- Get started
- Output approaches
- Dynamic Views
- Partial and Layouts
- Changelog
- References
- License
- About
Installation
First, in order to include it to your Maven project, simply add this dependency:
<dependency>
<groupId>com.github.xmlet</groupId>
<artifactId>htmlflow</artifactId>
<version>3.9</version>
</dependency>
You can also download the artifact directly from Maven Central Repository
Getting started
All methods (such as body()
, div()
, p()
, etc) return the created element,
except text()
which returns its parent element (e.g. .h1().text("...")
returns
the H1
parent object).
The same applies to attribute methods - attr<attribute name>()
- that also
return their parent (e.g. .img().attrSrc("...")
returns the Img
parent object).
There is also a special method __()
which returns the parent element.
This method is responsible for emitting the end tag of an element.
The HTML resulting from HtmlFlow respects all HTML 5.2 rules (e.g. h1().div()
gives a compilation error because it goes against the content
allowed by h1
according to HTML5.2). So, whenever you type .
after an element
the intelissense will just suggest the set of allowed elements and attributes.
The HtmlFlow API is according to HTML5.2 and is generated with the support of an automated framework (xmlet) based on an XSD definition of the HTML5.2 syntax.
Thus, all attributes are strongly typed with enumerated types which restrict the set of accepted values. Finally, HtmlFlow also supports dynamic views with data binders that enable the same HTML view to be bound with different object models.
Output approaches
Consider the definition of the following view that is late rendered by the function
passed to the view()
method:
static HtmlView view = StaticHtml.view(v -> v
.html()
.body()
.p().text("Typesafe is awesome! :-)").__()
.__() //body
.__()); // html
Thus you can get the resulting HTML in three different ways:
- get the resulting
String
through itsrender()
method or - directly write to any
Printstream
such asSystem.out
or - any other
PrintStream
chain such asnew PrintStream(new FileOutputStream(path))
.
NOTE: PrintStream
is not buffered, so you may need to interleave a BufferedOutputStream
object to improve performance.
On the other hand render()
internally uses a StringBuilder
which shows better speedup.
String html = view.render(); // 1) get a string with the HTML
view
.setPrintStream(System.out)
.write(); // 2) print to the standard output
view
.setPrintStream(new PrintStream(new FileOutputStream("details.html")))
.write(); // 3) write to details.html file
Desktop.getDesktop().browse(URI.create("details.html"));
Regardless the output approach you will get the same formatted HTML document:
<!DOCTYPE html>
<html>
<body>
<p>
Typesafe is awesome! :-)
</p>
</body>
</html>
Dynamic Views
A dynamic view is based on a template function BiConsumer<DynamicHtml<U>, U>
, i.e.
a void
function that receives a dynamic view (DynamicHtml<U>
) and a domain object of type U
--
(DynamicHtml<U>, U) => void
.
Given the template function we can build a dynamic view through DynamicHtml.view(templateFunction)
.
Next we present an example of a view with a template (e.g. taskDetailsTemplate
) that will be later
bound to a domain object Task
.
Note the use of the method dynamic()
inside the taskDetailsTemplate
whenever we are
binding properties from the domain object Task
.
This is a mandatory issue to enable dynamic bind of properties, otherwise those values are
cached and the domain object Task
will be ignored on further renders.
HtmlView<Task> view = DynamicHtml.view(HtmlLists::taskDetailsTemplate);
static void taskDetailsTemplate(DynamicHtml<Task> view, Task task) {
view
.html()
.head()
.title().text("Task Details").__()
.__() //head
.body()
.dynamic(body -> body.text("Title:").text(task.getTitle()))
.br().__()
.dynamic(body -> body.text("Description:").text(task.getDescription()))
.br().__()
.dynamic(body -> body.text("Priority:").text(task.getPriority()))
.__() //body
.__(); // html
}
Next we present an example binding this same view with different domain objects, producing different HTML documents.
List<Task> tasks = Arrays.asList(
new Task(3, "ISEL MPD project", "A Java library for serializing objects in HTML.", Priority.High),
new Task(4, "Special dinner", "Moonlight dinner!", Priority.Normal),
new Task(5, "US Open Final 2018", "Juan Martin del Potro VS Novak Djokovic", Priority.High)
);
for (Task task: tasks) {
Path path = Paths.get("task" + task.getId() + ".html");
Files.write(path, view.render(task).getBytes());
Desktop.getDesktop().browse(path.toUri());
}
Finally, an example of a dynamic HTML table binding to a list of tasks:
static void tasksTableTemplate(DynamicHtml<Stream<Task>> view, Stream<Task> tasks) {
view
.html()
.head()
.title()
.text("Tasks Table")
.__()
.__()
.body()
.table()
.attrClass("table")
.tr()
.th().text("Title").__()
.th().text("Description").__()
.th().text("Priority").__()
.__()
.tbody()
.dynamic(tbody ->
tasks.forEach(task -> tbody
.tr()
.td().dynamic(td -> td.text(task.getTitle())).__()
.td().dynamic(td -> td.text(task.getDescription())).__()
.td().dynamic(td -> td.text(task.getPriority().toString())).__()
.__() // tr
) // forEach
) // dynamic
.__() // tbody
.__() // table
.__() // body
.__(); // html
}
static HtmlView<Stream<Task>> tasksTableView = DynamicHtml.view(HtmlForReadme::tasksTableTemplate);
public class App {
public static void main(String [] args) throws IOException {
Stream<Task> tasks = Stream.of(
new Task(3, "ISEL MPD project", "A Java library for serializing objects in HTML.", Priority.High),
new Task(4, "Special dinner", "Moonlight dinner!", Priority.Normal),
new Task(5, "US Open Final 2018", "Juan Martin del Potro VS Novak Djokovic", Priority.High)
);
Path path = Paths.get("tasksTable.html");
Files.write(path, tasksTableView.render(tasks).getBytes());
Desktop.getDesktop().browse(path.toUri());
}
}
References
- 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.