# Kotlin and Java

> The builder functions with enums, and LO-TEK's check in ordinary tests, on the JVM without Node and without JavaScript.

LO-TEK for Kotlin gives you three things: the builder functions from `Lt`, where the allowed values are `enum`s; the same check that `lotek check` and the editor use, as an ordinary function in your tests; and the CSS and the web components as a WebJar. Everything runs in the JVM or is served by the app. No Node, no npm, no process to start and nothing to install alongside.

```kotlin title="build.gradle.kts"
dependencies {
    implementation("io.github.markusaugust:lo-tek-interfaces:0.40.0")
}
```

The version is the same as the npm package’s, so a version number means the same LO-TEK in Maven and on npm. But not every npm version goes to Maven Central. If the version above is not [on Central](https://central.sonatype.com/artifact/io.github.markusaugust/lo-tek-interfaces), use the newest one that is. The package has one dependency, `com.dylibso.chicory:runtime`, and requires Java 17 or newer.

## The CSS and the web components

The package is also a [WebJar](https://www.webjars.org): `lotek.css`, the stylesheet for each component and the JavaScript modules for the browser are under `META-INF/resources/webjars/lo-tek/0.40.0/`, with the same paths as in the npm package. Spring Boot and Servlet containers serve that folder without setup. In Ktor it is one line:

```kotlin
routing {
    staticResources("/webjars", "META-INF/resources/webjars")
}
```

`LotekWebJar` has the paths, with the version in them, so the template never points to a different version than the one the package has:

```kotlin
"""
<link rel="stylesheet" href="${LotekWebJar.CSS}">
<script type="module">
  import { defineLt } from "${LotekWebJar.REGISTER}"
  defineLt()
</script>
"""
```

An address from the component pages keeps its path and only changes the beginning. This one:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/src/components/css/button/button.css">
```

becomes `${LotekWebJar.ROOT}/src/components/css/button/button.css`. What is only for Node or React is not included.

## The builder functions

`Lt` has the same builder functions and options as `lt` in TypeScript. Each gives the attributes you put on the element:

```kotlin
import no.lotek.*


val save = Lt.button(variant = ButtonVariant.SECONDARY)


"""<button ${save.toHtml()}>Save draft</button>"""
// <button class="lt-button" data-variant="secondary">Save draft</button>
```

The allowed values are `enum`s, so `ButtonVariant.SECUNDARY` is a compile error, and the IDE shows what exists. Values that recur have one shared name: `Size`, `FieldState` and `RequiredMarker` are used by every builder function that has them. Every `enum` has `value`, the text that ends up in the HTML, and `of(text)`, which gives the value, or `null` for a text from outside.

The result is an `Attributes`, which is an ordinary `Map<String, String>`. It can be given to any template engine. Without a template engine, `toHtml()` writes it out with the values escaped, and `+` combines two sets.

The builder functions that give several sets give a data class with one field per set. `Lt.field` wires the label, control, help text and error text:

```kotlin
val field = Lt.field(id = "fixer", required = RequiredMarker.TEXT, help = true, error = true, invalid = invalid)


"""
<label ${field.label.toHtml()}>Fixer's address</label>
<input ${(Lt.input(type = InputType.EMAIL) + field.control).toHtml()}>
<p ${(Lt.helpText() + field.help).toHtml()}>The board writes to the fixer, never to the crew.</p>
<p ${(Lt.errorText() + field.error).toHtml()}>Enter the fixer's address, for example fixer@relay.example.</p>
"""
```

A flag such as `hidden` has the empty text as its value, and is written as the name alone.

The id is required, and an empty one throws. Make it from a key in the data, never from the loop index or a counter, so it is unique on the page and the same on every render: a morph recognises an element by its id, and replaces one whose id changed, along with what the user typed. See [Ids that survive an update](/markup-and-behaviour.md#ids-that-survive-an-update).

The parts a template writes itself, such as the heading in a card or an option in a toggle group, have their classes on the builder function, with the same names as in TypeScript. Each is a set with the class, so it goes where every other answer goes. The exception is a part the answer already gives, such as the header of `Lt.dialog`: there the set in the answer is the way, and the constant TypeScript also has is left out.

```kotlin
"""
<div ${Lt.card().toHtml()}>
  <h3 ${Lt.card.title.toHtml()}>Job 222-0481</h3>
</div>
"""
```

### Same answers as in TypeScript

The builder functions are generated from the manifest LO-TEK writes from the TypeScript code, and the few where the options interact, such as `field` and `tabs`, are written by hand. All are tested against more than a thousand build cases with the answer from TypeScript. If one builder function gives one attribute differently, the package is not released.

### From Java

The builder functions are static methods on `Lt`, and the options can be left out from the back:

```java
import no.lotek.*;


Attributes save = Lt.button(ButtonVariant.SECONDARY);
FieldParts field = Lt.field("fixer");
ButtonVariant variant = ButtonVariant.of(request.getParameter("variant"));
```

An option in the middle of the list needs the ones before it, as `null` when they are left out. `JavaCallTest` compiles these calls in Java on every build.

### In the editor

The documentation on each builder function, the options with their defaults and a link to the component page, comes from the same manifest, so IntelliJ shows it in Quick Documentation on `Lt.button(…)` from the sources jar.

## kotlinx.html

`io.github.markusaugust:lo-tek-kotlinx-html` has one extension, `lt(…)`, which puts the attributes from the builder functions on a tag, and one function per web component, such as `ltField { … }` for `<lt-field>`, since kotlinx.html knows only the elements in HTML. It is a separate package, so the main package does not bring kotlinx.html to those who do not use it.

```kotlin
import kotlinx.html.*
import no.lotek.*
import no.lotek.InputType
import no.lotek.html.lt


fun FlowContent.fixerField(fixer: String, invalid: Boolean) {
    val field = Lt.field(id = "fixer", required = RequiredMarker.TEXT, help = true, error = true, invalid = invalid)
    label { lt(field.label); +"Fixer's address" }
    input { lt(Lt.input(type = InputType.EMAIL), field.control); name = "fixer"; required = true; value = fixer }
    p { lt(Lt.helpText(), field.help); +"The board writes to the fixer, never to the crew." }
    p { lt(Lt.errorText(), field.error); +"Enter the fixer's address, for example fixer@relay.example." }
}
```

Every component page has a kotlinx.html tab in this form. `RecipesTest` compiles every one, renders it, and compares it with the HTML recipe on the same page.

Several sets are applied in order, and a later one wins, like `{...a} {...b}` in JSX. The classes are added to those the tag has, so `div(classes = "my-own") { lt(Lt.card()) }` gives both. `InputType` is imported on its own, since kotlinx.html has one with the same name.

### With Ktor

The whole round trip, with the form from the [error summary](/components/error-summary.md) page. `GET /jobs` renders the form. `POST /jobs` checks the submission, and a failed one gets the form back with status 422, with what was typed and the summary filled in, and the component moves focus to the summary when the page arrives. A valid one is saved, and the browser is sent to `GET /jobs?ordered`, so a reload does not post it again, and that page says the job was ordered. The only script is the registration, once in the page.

```kotlin
// build.gradle.kts, next to the Ktor 3 server and lo-tek-kotlinx-html
dependencies {
    implementation("io.ktor:ktor-server-html-builder:$ktorVersion")
}
```

```kotlin
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.Application
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.staticResources
import io.ktor.server.request.receiveParameters
import io.ktor.server.response.respondRedirect
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.routing
import kotlinx.html.*
import no.lotek.*
import no.lotek.InputType
import no.lotek.html.lt
import no.lotek.html.ltErrorSummary


data class Fault(val field: String, val message: String)


// The faults for what was submitted. Empty when the job can be ordered.
fun faultsIn(fixer: String): List<Fault> =
    if (fixer.contains("@")) emptyList()
    else listOf(Fault("fixer", "Enter the fixer's address with an @"))


fun FlowContent.orderForm(fixer: String = "", faults: List<Fault> = emptyList()) {
    val summary = Lt.errorSummary(count = faults.size, id = "errors")
    val field = Lt.field(id = "fixer", invalid = faults.any { it.field == "fixer" })
    form(action = "/jobs", method = FormMethod.post) {
        // The server checks the address. The browser's own check of
        // type="email" would stop the submission before it got there.
        attributes["novalidate"] = ""
        ltErrorSummary {
            lt(summary.host)
            h2 {
                lt(summary.title)
                if (faults.isNotEmpty()) +(if (faults.size == 1) "The form has one error" else "The form has ${faults.size} errors")
            }
            ul {
                lt(Lt.list())
                for (fault in faults) li { a(href = "#${fault.field}") { +fault.message } }
            }
        }
        label { lt(field.label); +"Fixer's address" }
        input {
            lt(Lt.input(type = InputType.EMAIL), field.control)
            name = "fixer"
            if (fixer.isNotEmpty()) value = fixer
        }
        button { lt(Lt.button()); +"Order the job" }
    }
}


fun HTML.jobsPage(ordered: Boolean = false, content: FlowContent.() -> Unit) {
    attributes["lang"] = "en"
    head {
        title("Order a job")
        link(rel = "stylesheet", href = LotekWebJar.CSS)
    }
    body {
        if (ordered) div { lt(Lt.alert(color = AlertColor.SUCCESS)); p { +"The job has been ordered." } }
        content()
        script(type = "module") { unsafe { +"import { defineLt } from \"${LotekWebJar.REGISTER}\"; defineLt()" } }
    }
}


fun Application.jobs() {
    routing {
        staticResources("/webjars", "META-INF/resources/webjars")
        get("/jobs") {
            val ordered = call.request.queryParameters.contains("ordered")
            call.respondHtml { jobsPage(ordered) { orderForm() } }
        }
        post("/jobs") {
            val fixer = call.receiveParameters()["fixer"].orEmpty().trim()
            val faults = faultsIn(fixer)
            if (faults.isEmpty()) {
                // Save the job here.
                call.respondRedirect("/jobs?ordered")
            } else {
                call.respondHtml(HttpStatusCode.UnprocessableEntity) { jobsPage { orderForm(fixer, faults) } }
            }
        }
    }
}
```

The forms on the component pages post, so a real app needs the protection against cross-site request forgery it already has. kotlinx.html writes no token by itself, as a template engine with a security integration might. With Spring Security, pass the `CsrfToken` into the function and write it as a hidden field in each form, `hiddenInput(name = csrf.parameterName) { value = csrf.token }`. A script that posts with `fetch` cannot see the Kotlin value, so render it into the page as well, `meta(name = "_csrf", content = csrf.token)` and `meta(name = "_csrf_header", content = csrf.headerName)` in `head`, and send it as that header from there, as Spring Security’s own documentation does. Ktor’s CSRF plugin, `io.ktor:ktor-server-csrf`, checks the `Origin` header rather than a token, so the forms need nothing written in them: `install(CSRF) { originMatchesHost() }`, or `allowOrigin("https://…")` behind a proxy that changes the host. Leave out `checkHeader`, which requires a header a plain form post never sends.

## Templates: JTE, Thymeleaf and the others

A template cannot call `Lt` for every value, and then the check is what keeps watch. Write the markup as on the component pages, and check what the template gives in a test. With [JTE](https://jte.gg):

```kotlin
@Test
fun `the order form is correct markup`() {
    val out = StringOutput()
    templateEngine.render("order.kte", mapOf("invalid" to true), out)
    assertLotek(out.toString())
}
```

The same goes for Thymeleaf, Freemarker and Pebble: render the template to a text, and give it to `assertLotek`. The check also understands the template syntax in the template file itself, such as `${…}`, `th:…` and `@if`, so the [editor](/the-editor.md) can tell you while you type.

## The check in tests

`assertLotek` checks the HTML the server sends, and fails with the findings as its message:

```kotlin
@Test
fun `the form is wired correctly`() {
    assertLotek(app.render("/form"))
}
```

```text
LO-TEK found 2 findings:
12:30: error: for="fixer" points at the id "fixer", which does not exist on the page. … [id-not-found]
18:9: warning: The class "lt-buton" does not exist in LO-TEK. Did you mean lt-button? [unknown-class]
```

It throws an `AssertionError`, which JUnit, Kotest and TestNG all understand. By default a whole page is checked: that every id that is pointed to exists, and that every field is wired. If the HTML is a piece of a page, such as a component, `assertLotek(html, fragment = true)` checks only the vocabulary.

It works the same for everything that makes HTML on the JVM: strings, kotlinx.html, Thymeleaf, JTE, Ktor and Spring. The check reads the HTML that comes out, not the code that makes it.

If you need the findings yourself, `Lotek.diagnosePage(html)` and `Lotek.diagnoseMarkup(html)` give them as a list. Each finding has `start` and `end`, `line` and `column`, `severity`, `rule`, `message`, `link` and sometimes a `fix`. The positions are indices in a Kotlin `String`, so `html.substring(finding.start, finding.end)` gives exactly the text the finding is about, also with “æøå” and emoji in front.

A finding you want to keep is suppressed with a comment above the tag:

```html
<!-- lotek-ignore-next unknown-class -->
<div class="lt-experiment">…</div>
```

Without a rule name, the comment applies to all the rules for the next tag.

## The rendered page

With Playwright for Java, the page is checked as the browser has rendered it, with the markup the web components added and the stylesheets it loaded. The package does not depend on Playwright: `assertLotekRendered` takes a function that runs a script on the page.

```kotlin
@Test
fun `the form is right as it is shown`() {
    page.navigate("http://localhost:8080/form")
    assertLotekRendered { script -> page.evaluate(script) }
}
```

It fails on what `assertLotek` fails on, and in addition on a class no stylesheet styles and a value without a rule. If you have the stylesheets as text, `assertLotek(html, css = listOf(stylesheet))` and `Lotek.diagnosePage(html, css)` give the same check without a browser.

If the project has taken over components, `diagnoseMarkup` and `diagnosePage` take the manifest as text in `manifest`, and then check against the same as the Gradle plugin and the editor. Without the option, the manifest in the package applies. `assertLotek` and `assertLotekRendered` do not take it yet.

```kotlin
val manifest = File("build/lotek/manifest.json").readText()
assertEquals(emptyList(), Lotek.diagnosePage(html, manifest = manifest))
```

The file is written by `lotekManifest`, which is run by `assemble` and not by `test`. If the tests are to read it, they must wait for the task: `tasks.test { dependsOn("lotekManifest") }`.

## The theme

`Lotek.buildTheme(recipe)` builds the same colour theme as [`lotek theme`](/command-line.md#theme), from the JSON text of a recipe file, and `Lotek.inspectTheme(css)` checks a theme you have written yourself, like `lotek check-theme`. The recipe has the nine colours, `typography` and `shape`:

```kotlin
val theme = Lotek.buildTheme("""{"accent": "#7c3aed", "danger": "#b3261e"}""")
File("build/lotek/theme.css").writeText(theme.css)
```

The result has `light` and `dark`, each `--lt-color-*` token with its value, `violations` and `css`. The promises hold by construction, so `violations` is empty for valid colours, but check it before you write the file: `lotek theme` refuses to write when it is not empty, while `buildTheme` returns the theme with the list, as in TypeScript. A key that does not exist, and a recipe the core rejects, throw `IllegalArgumentException` with the explanation. `inspectTheme` gives `problems` with selector and message, and the numbers `blocks`, `declarations` and `promises`, which say how much was actually read. The recipe is read the way `lotek theme` reads it, and both give the answer the core gives in TypeScript, tested against the same expected results.

## The Gradle plugin

The template files themselves can be checked as part of the build, without a test that renders them. The plugin runs the same command line as `lotek check` and `lotek theme`, in the Gradle process:

```kotlin title="build.gradle.kts"
plugins {
    id("io.github.markusaugust.lo-tek") version "0.40.0"
}


lotek {
    templates.from(fileTree("src/main/jte"))
    themeRecipe = file("lotek.theme.json")
}
```

| Task | Does |
| - | - |
| `lotekCheck` | checks `templates` like `lotek check`, and fails with the findings. Run by `check`. |
| `lotekTheme` | builds the theme from `themeRecipe` to `themeOutput`, by default `build/lotek/theme.css` |
| `lotekManifest` | writes the manifest the project is checked against to `build/lotek/manifest.json` when `manifests` has fragments, and deletes the file when it has none. Run by `assemble`. |

```text
> Task :lotekCheck FAILED
The markup does not match LO-TEK:


src/main/jte/order.jte:14:18: warning: The class "lt-buton" does not exist in LO-TEK. Did you mean lt-button?
```

`rendered = true` checks the files as whole pages, with the wiring, like `--rendered`. If you have taken over a component with [`lotek takeover`](/command-line.md#takeover), you put the fragment in `manifests`, so the markup for the copy is checked too, and the copy is included in `build/lotek/manifest.json`:

```kotlin
lotek {
    templates.from(fileTree("src/main/jte"))
    manifests.from("src/main/lotek/button/lotek-manifest.json")
}
```

The plugin is on Maven Central together with the package, with the same version. Gradle finds it there without a separate `pluginManagement` block, since the Gradle Plugin Portal forwards to Maven Central for what it does not have itself. The tasks work with the configuration cache, and are up to date when neither the templates nor the fragments have changed.

## How it works

The check is written in Rust and built to one WebAssembly module. The same module runs in Node, in VS Code and here. On the JVM it is run by [Chicory](https://chicory.dev), a WebAssembly runtime written in pure Java. The module was compiled to JVM bytecode when the package was built, so it runs with the JIT and starts in a fraction of a second. A call borrows an instance from a pool and returns it afterwards, so tests that run at the same time do not wait for each other.

## In IntelliJ

The plugin [LO-TEK Interfaces](https://plugins.jetbrains.com/plugin/34764-lotek) runs the same check while you type, also in HTML in a Kotlin string marked with `@Language("HTML")` or `// language=HTML`. See [The editor](/the-editor.md#jetbrains).

With the builder functions it does four more things in Kotlin:

- completion shows the description and the default of each option next to it;
- it warns about a set on an element its part cannot be, like `label { lt(field.control) }`, and moves it where exactly one element can take it;
- it warns about `ButtonVariant.of(value)!!`, which throws without saying what the value was, about an id from the loop index, a random value or the same text in every row, and about `Lt.field(error = true)` without `field.error`;
- `ltfield`, `lttabs` and one for each of the other web components, at the top level of a file, insert the Kotlin recipe from the component page, with the ids left for you to fill in.
