上次看過怎麼生成路由之後
今天我們來看看 Ktor 產生前端畫面的流程是怎麼進行的
Ktor 裡面也支援 Java 常見的 HTML 樣板
比方說 Freemarker、Mustache、Thymeleaf、Velocity 等等
不過最值得特別提出來說的
應該是 Ktor 自己定義的 HTML DSL 和 CSS DSL
我們來看看生成 HTML 的程式碼怎麼寫
get("/more-rows") {
call.respondHtml {
body {
table {
tbody {
randomRows(random)
}
}
}
}
}
這邊我們可以看到,利用 Kotlin 的 trailing lambda 特性,我們可以將生成畫面的函式設計成互相包含的結構,
每個函式包含著生成其他元件的函式,並且將調整的參數寫在 lambda 裡面,幾乎就跟對應的 HTML 結構一樣了。
甚至我們可以定義自己的元件,像是這樣
get("/") {
call.respondHtml {
leaderboardPage(random)
}
}
然後我們在另一個檔案內定義 leaderboardPage()
fun HTML.leaderboardPage(random: Random) {
head {
title("HTMX Example")
script(src = "/web.js") {}
link(rel = "stylesheet", href = "/leaderboard.css")
}
body {
h1 {
+"Leaderboard"
}
table {
id = "leaderboard"
thead {
tr {
th { +"Alias" }
th { +"Score" }
}
}
tbody {
randomRows(random)
}
}
h2 {
id = "total-count"
+"Total: 10"
}
}
}
要生成 CSS 的話,我們可以先用類似的語法生成
get("/styles.css") {
call.respondCss {
body {
backgroundColor = Color.darkBlue
margin = Margin(0.px)
}
rule("h1.page-title") {
color = Color.white
}
}
}
suspend inline fun ApplicationCall.respondCss(builder: CssBuilder.() -> Unit) {
this.respondText(CssBuilder().apply(builder).toString(), ContentType.Text.CSS)
}
接著在程式內引入 styles.css
get("/html-css-dsl") {
call.respondHtml {
head {
link(rel = "stylesheet", href = "/styles.css", type = "text/css")
}
body {
h1(classes = "page-title") {
+"Hello from Ktor!"
}
}
}
}
這背後的原理是什麼呢?首先我們挑 head
來看實作
@HtmlTagMarker
@OptIn(ExperimentalContracts::class)
inline fun HTML.head(crossinline block : HEAD.() -> Unit = {}) : Unit {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
HEAD(emptyMap, consumer).visit(block)
}
這個函數只接受一個參數,就是以 HEAD 為接收者的 Lambda。另外如果沒有傳入 block
的話,就傳入一個空的 lambda。
不管我們有沒有傳入 block
,之後都會執行 HEAD(emptyMap, consumer).visit(block)
inline fun <T : Tag> T.visit(crossinline block: T.() -> Unit) = visitTag { block() }
visitTag
則是
inline fun <T : Tag> T.visitTag(block: T.() -> Unit) {
consumer.onTagStart(this)
this.block()
consumer.onTagEnd(this)
}
到這邊我們就可以大致知道,利用函數一層一層的包起來,所以我們就可以先生成 HTML 開頭的標籤,運作完裡面的內容(也是生成許多 HTML 標籤),之後生成結尾的標籤。
這樣的實作方式,不僅不需要 template engine 也可以生成複雜的畫面,另外由於全部都是 Kotlin 的程式,所以不用擔心樣板內使用程式碼的耦合問題。
今天的部分就到這邊,我們明天見!