Skip to content

Commit

Permalink
Shelly component using htmx and reorganization
Browse files Browse the repository at this point in the history
  • Loading branch information
DonRobo committed Jul 21, 2024
1 parent e415bd5 commit 2100ef0
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 233 deletions.
6 changes: 5 additions & 1 deletion web/src/main/kotlin/at/robert/hf/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package at.robert.hf
import at.robert.hf.config.configureErrors
import at.robert.hf.config.configureStaticContent
import at.robert.hf.page.configureConfigPage
import at.robert.hf.page.configureHtmxPage
import at.robert.hf.page.configureShellyManager
import at.robert.hf.page.registerShellyPage
import io.ktor.server.application.*

fun main(args: Array<String>) {
Expand All @@ -14,8 +16,10 @@ fun Application.module() {
configureErrors()
configureStaticContent()
configureConfigPage()

configureShellyManager()
registerShellyPage()

// configureHtmxPage()
configureHtmxPage()
}

10 changes: 10 additions & 0 deletions web/src/main/kotlin/at/robert/hf/htmx/HtmxComponent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package at.robert.hf.htmx

import io.ktor.http.*
import kotlinx.html.TagConsumer

interface HtmxComponent {
val id: String

suspend fun render(render: TagConsumer<*>, params: Parameters, hxCtx: HtmxContext)
}
6 changes: 6 additions & 0 deletions web/src/main/kotlin/at/robert/hf/htmx/HtmxContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package at.robert.hf.htmx

data class HtmxContext(
val route: String,
val component: String,
)
18 changes: 18 additions & 0 deletions web/src/main/kotlin/at/robert/hf/htmx/HtmxPage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package at.robert.hf.htmx

import kotlinx.html.TagConsumer
import kotlinx.html.h1

abstract class HtmxPage {
open suspend fun renderHeader(render: TagConsumer<*>) {
val title = title()
render.h1 {
+title
}
}

abstract suspend fun title(): String

abstract suspend fun components(): List<HtmxComponent>
open suspend fun getComponent(id: String): HtmxComponent? = components().find { it.id == id }
}
91 changes: 91 additions & 0 deletions web/src/main/kotlin/at/robert/hf/htmx/HtmxRenderer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package at.robert.hf.htmx

import at.robert.hf.respondHtmlBody
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.html.div
import kotlinx.html.id
import kotlinx.html.main
import kotlinx.html.stream.appendHTML
import kotlinx.html.unsafe

object HtmxRenderer {
private suspend fun renderHtmxPage(
page: HtmxPage,
currentRoute: String,
call: ApplicationCall
) = coroutineScope {
val title = page.title()

val headerD = async {
buildString {
page.renderHeader(appendHTML())
}
}

val renderedComponentsD = page.components().associateWith { comp ->
async {
buildString {
comp.render(appendHTML(), Parameters.Empty, HtmxContext(currentRoute, comp.id))
}
}
}

val header = headerD.await()
val renderedComponents = renderedComponentsD.mapValues { it.value.await() }

call.respondHtmlBody(title, includeHtmx = true) {
main {
unsafe {
raw(header)
}
renderedComponents.forEach { (component, content) ->
div {
id = component.id
unsafe {
raw(content)
}
}
}
}
}
}

private suspend fun renderHtmxComponent(
page: HtmxPage,
compId: String,
currentRoute: String,
call: ApplicationCall
) = coroutineScope {
val paramsD = async { call.receiveParameters() }
val componentD = async { page.getComponent(compId)!! }
val text = buildString {
val component = componentD.await()
component.render(appendHTML(), paramsD.await(), HtmxContext(currentRoute, component.id))
}
call.respond(TextContent(text, ContentType.Text.Html.withCharset(Charsets.UTF_8)))
}

fun registerPage(routing: Route, route: String, page: HtmxPage) {
registerPage(routing, route) { page }
}

fun registerPage(routing: Route, route: String, block: (Parameters) -> HtmxPage) {
routing.get(route) {
val page = block(call.parameters)
renderHtmxPage(page, call.request.path(), call)
}
routing.post(route) {
val page = block(call.parameters)
require(call.request.header("HX-Request") == "true")
val compId = call.request.header("HX-Target")!!
renderHtmxComponent(page, compId, call.request.path(), call)
}
}
}
15 changes: 15 additions & 0 deletions web/src/main/kotlin/at/robert/hf/htmx/SimpleHtmxComponent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package at.robert.hf.htmx

import io.ktor.http.*
import kotlinx.html.TagConsumer

class SimpleHtmxComponent(
override val id: String,
private val block: (TagConsumer<*>) -> Unit
) : HtmxComponent {
override suspend fun render(render: TagConsumer<*>, params: Parameters, hxCtx: HtmxContext) {
block(render)
}
}

fun simpleComponent(id: String, block: TagConsumer<*>.() -> Unit) = SimpleHtmxComponent(id, block)
30 changes: 30 additions & 0 deletions web/src/main/kotlin/at/robert/hf/htmx/util.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package at.robert.hf.htmx

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.html.*

val hxObjectMapper = jacksonObjectMapper()
fun Tag.hxPost(hxCtx: HtmxContext, vararg params: Pair<String, Any>) {
attributes["hx-post"] = hxCtx.route
attributes["hx-target"] = "#${hxCtx.component}"
attributes["hx-swap"] = "innerHTML"
attributes["hx-vals"] = params.toMap().let { hxObjectMapper.writeValueAsString(it) }
}

fun FlowContent.hxActionForm(
hxCtx: HtmxContext,
actionLabel: String,
block: FORM.() -> Unit
) {
form {
attributes["hx-post"] = hxCtx.route
attributes["hx-target"] = "#${hxCtx.component}"
attributes["hx-swap"] = "innerHTML"

block()
input(type = InputType.submit) {
value = actionLabel
}
}
}

120 changes: 0 additions & 120 deletions web/src/main/kotlin/at/robert/hf/page/HtmxPage.kt

This file was deleted.

48 changes: 48 additions & 0 deletions web/src/main/kotlin/at/robert/hf/page/HtmxTestPage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package at.robert.hf.page

import at.robert.hf.htmx.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import kotlinx.html.TagConsumer
import kotlinx.html.button
import kotlinx.html.div
import kotlinx.html.p

object HtmxTestPage : HtmxPage() {
object BasicHtmxComponent : HtmxComponent {
override val id: String
get() = "comp1"

override suspend fun render(render: TagConsumer<*>, params: Parameters, hxCtx: HtmxContext) {
render.apply {
p { +"This is a component" }
params.names().forEach {
div {
+"$it: ${params[it]}"
}
}
button {
hxPost(
hxCtx,
"greeting" to "Hello hxPost!",
)
+"Click me"
}
}
}
}

override suspend fun title() = "Htmx Test Page"

override suspend fun components(): List<HtmxComponent> {
return listOf(BasicHtmxComponent)
}

}

fun Application.configureHtmxPage() {
routing {
HtmxRenderer.registerPage(this, "/htmx", HtmxTestPage)
}
}
Loading

0 comments on commit 2100ef0

Please sign in to comment.