Kotlin-htmx

Today I will talk about why I think Kotlin and htmx mix so well. You can follow along (or skip the article) with the code here (there is also a static generation example in the static branch there). Try the demo here.

Kotlin as you may know is a modern JVM and (more recently) multiplatform language. It has a great type system and interesting advanced features while still being a pragmatic tool meant for engineers.

htmx is a JavaScript library that allows you to write HTML attributes instead of JS code to load content from the server and manipulate the DOM. Your HTML will be more powerful than ever and you will deal less with JS.

Let's start! The main function creates a Javalin HTTP Server (static files config including Webjars which is how we will import the htmx js code avoiding node) and adds the "Friends" module (which is an extension function). Finally, it starts the server. A key config I want to highlight is the webjars setup that will help us serve htmx:

// https://github.com/corlaez/kotlin-htmx/blob/master/src/main/kotlin/javalin.kt
config.addStaticFiles { staticFiles ->
    staticFiles.directory = "META-INF/resources/webjars/"
    staticFiles.location = Location.CLASSPATH
}

It is important to mention that Javalin can be replaced with any HTTP Server you like. In fact your can drop the server altogether, write your HTML Strings to files and make your own static generator (that's how corlaez.com is written)

Then addFriendsModule creates an index route and a route for each friend. ctx.html is the Javalin function to respond with an HTML string. friends.forEach will be inlined into a regular for loop

// https://github.com/corlaez/kotlin-htmx/blob/master/src/main/kotlin/friends/addFriendsModule.kt
fun Javalin.addFriendsModule() {
    val friends = listOf("Joseph", "Nicholas", "Caesar")
    get("/") { ctx ->
        ctx.html(htmlFriendsIndex(friends))
    }
    friends.forEach { friend ->
        get("/$friend") { ctx ->
            val characteristic = listOf("swimmer", "runner", "debater").random()
            ctx.html(htmlFriendProfile(friend, characteristic))
        }
    }
}

Next we have the base html template which is a Kotlin function. The parameter is a lambda with receiver that will populate our HTML body tag and the return is an String. createHTML takes out fancy DSL and outputs a String as well. If you try to define a body tag inside a head or viceversa the editor and compiler will let you know.

// https://github.com/corlaez/kotlin-htmx/blob/master/src/main/kotlin/htmlBaseTemplate.kt
fun htmlBaseTemplate(bodyFn: BODY.() -> Unit): String {
    return "<!DOCTYPE html>" + createHTML().html {
        lang = "en"
        head {
            script { src = "/htmx.org/$htmxVersion/dist/htmx.js" }
            link { href = "/modest-variation.css"; rel="stylesheet" }
        }
        body {
            bodyFn()
        }
    }
}

Let's now focus on htmlFriendProfile and htmlFriendsIndex now:

// https://github.com/corlaez/kotlin-htmx/blob/master/src/main/kotlin/friends/html.kt
fun htmlFriendsIndex(friends: List<String>): String {
    return htmlBaseTemplate {
        h1 { a { href = "/"; +"My Friends" } }// nested `a` inside a `h1`. To insert a text inside any tag we use the + operator
        friends.forEach { friend ->// Regular Kotlin used to loop!
            h2 {
                a {
                    href = "/$friend"// Sets cursor pointer and works without js
                    hxGet("/$friend")
                    hxSwap("outerHTML")
                    hxTarget("closest h2")
                    +friend
                }
            }
        }
    }
}

fun htmlFriendProfile(friend: String, characteristic: String): String {
    return createHTML().article { // createHTML will take all the DSL and produce a String
        h2(classes = "your-css-class") {
            +friend
        }
        p {
            +"My friend $friend is smart and a good $characteristic"
        }
    }
}

htmlFriendsIndex calls htmlBaseTemplate as this is meant to be a full page. This index builds its contents using regular Kotlin loops. htmlFriendProfile, on the other hand, creates the HTML directly as it is just a partial HTML document.

The hx functions are simple HTMLTag extension functions that add HTML attributes.

// https://github.com/corlaez/kotlin-htmx/blob/master/src/main/kotlin/hx.kt
fun HTMLTag.hxGet(value: String) {
    attributes += "hx-get" to value
}
fun HTMLTag.hxSwap(value: String) {
    attributes += "hx-swap" to value
}
fun HTMLTag.hxTarget(value: String) {
    attributes += "hx-target" to value
}

The autocomplete game of HTML DSL gives me the best experience writing HTML I have ever experienced and the htmx integration is seamless. It is hard to articulate how good is it until you try it (Use IntelliJ IDEA if in doubt, there is a free community edition, and they do offer education licences if you have an institutional email)

Cons!

I hope this was interesting, have fun!