Skip to content

Latest commit

 

History

History
352 lines (291 loc) · 13.8 KB

Readme.md

File metadata and controls

352 lines (291 loc) · 13.8 KB

HtmlFlow

Build Status Maven Central Version Coverage Status javadoc HtmlApiFaster javadoc HtmlFlow

Petclinic Sample

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:

output  ↴
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
<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>
↸  Flowifier.fromHtml("...")

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

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() and raw() return the parent element (e.g. .h1().text("...") returns the H1 parent). .text()escapes HTML, while raw() doesn't.
  • attribute builders - attr<attribute name>() - return their parent (e.g. .img().attrSrc("...") returns the Img).
  • __() or l 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 element E, where E is the parent HTML element.
  • dynamic(BiConsumer<E, M> cons) - similar to .of() but the consumer receives an additional argument M (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

License

MIT

About

HtmlFlow was created by Miguel Gamboa (aka fmcarvalho), an assistant professor of Computer Science and Engineering of ISEL, Polytechnic Institute of Lisbon.