How do I provide a page template in kotlinx-html? - kotlin

I would like to generate a bunch of HTML files with kotlinx-html and I want to start each file with the same template. I would like to have a function for the base structure and provide a lamda to this function for the specific content like so (non working code):
// provide block as a div for the sub content, does not work!
private fun createHtmlPage(block : () -> DIV.()): String {
val html = createHTMLDocument().html {
head {
meta { charset = "utf-8" }
meta { name="viewport"; content="width=device-width, initial-scale=1" }
title { +"Tables" }
link(href = "https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css", "style")
}
body {
block {}
script("", "https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.bundle.min.js") {}
}
}
return html.serialize(true)
}
and use this function like this (again non working code):
private fun createIndexPage(tables: Tables) {
val indexFile = File(path, "index.html")
// call my template function with a lamda - does not work
val html = createHtmlPage {
h1 { +"Tables" }
tables.tableNames.forEach { tableName ->
a("${tableName}.html") {
+tableName
}
br
}
}
indexFile.writeText(html)
}
Can anyone point me in the direction how to do this?
Additional question
I have found out that project Ktor HTML DSL exists and they have template support on top of kotlinx-html. Am I supposed to use this library instead of kotlinx-html directly? Is it possible to use it without Ktor?

You don't need Ktor. It can be done with just kotlinx-html and plain Kotlin.
TLDR
Change block : () -> DIV.() to block : HtmlBlockTag.() -> Unit and change block {} to block(), so that the final code becomes:
private fun createHtmlPage(block: HtmlBlockTag.() -> Unit): String {
val html = createHTMLDocument().html {
head {
meta { charset = "utf-8" }
meta { name="viewport"; content="width=device-width, initial-scale=1" }
title { +"Tables" }
link(href = "https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css", "style")
}
body {
block()
script("", "https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.bundle.min.js") {}
}
}
return html.serialize(true)
}
So that you can use this function with code like this:
val result = createHtmlPage {
div {
h1 {
+"It is working!"
}
}
}
println(result)
Then the output will be:
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<title>Tables</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/css/bootstrap.min.css" rel="style">
</head>
<body>
<div>
<h1>It is working!</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.0/dist/js/bootstrap.bundle.min.js" type=""></script>
</body>
</html>
What's wrong with the code
Your first block of code has this signature:
private fun createHtmlPage(block : () -> DIV.()): String
This isn't valid Kotlin code, because the type of parameter block isn't valid. Instead of using type () -> DIV.() it should be DIV.() -> Unit. This is a special construct in Kotlin, called a function type with receiver, which allows you to call your createHtmlPage function with a lambda, in which the lambda is scoped to a receiver object of type DIV.
So the function should be changed to:
private fun createHtmlPage(block: DIV.() -> Unit): String
The second part that is not valid is this:
body {
block {}
}
Because the parameter called block is of type DIV.() -> Unit, it needs to have access to an argument of type DIV. You don't have to pass with argument like in a normal function call, like block(someDiv), but it still needs access to it. But you don't have an object of type DIV available in your code, but you do have an object of type BODY, which is created by the body function. So if you change the parameter type of block from DIV.() -> Unit to BODY.() -> Unit, then you can use the BODY object created by body function.
So you can change the createHtmlPage function to:
private fun createHtmlPage(block: BODY.() -> Unit): String
and then you can provide the object of type BODY like this to block:
body {
this#body.block(this#body)
}
which can be shortened to:
body {
this.block(this)
}
which can be shortened to:
body {
block()
}
This last shortening step may be difficult to understand, but it's like this: because body function accepts an function type with receiver of type BODY.() -> Unit, the lambda that you pass to the body function, will be scoped to the BODY class/type, so that lambda has access to members available in the BODY type. The BODY type normally doesn't have access to the block() function, but because block is of type BODY.() -> Unit, instances of BODY also have access to the block function as if block is a member of BODY.
Because the call to block() is scoped to an instance of BODY, calling block() behaves like calling aBodyInstance.block(), which is the same as block(aBodyInstance). Because of this, at this location in the code, you can also call is with just block().
Instead of using createHtmlPage(block: BODY.() -> Unit) you could also use createHtmlPage(block: HtmlBlockTag.() -> Unit) so that you can use it in other places.

You can use the ktor-server-html-builder library without a server. Here is an example:
import io.ktor.server.html.*
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
fun main() {
val template = LayoutTemplate().apply {
header {
+"Ktor"
}
content {
articleTitle {
+"Hello from Ktor!"
}
articleText {
+"Kotlin Framework for creating connected systems."
}
}
}
val builder = StringBuilder()
builder.appendHTML().html(block = {
with(template) { apply() }
})
println(builder.toString())
}
class LayoutTemplate: Template<HTML> {
val header = Placeholder<FlowContent>()
val content = TemplatePlaceholder<ContentTemplate>()
override fun HTML.apply() {
body {
h1 {
insert(header)
}
insert(ContentTemplate(), content)
}
}
}
class ContentTemplate: Template<FlowContent> {
val articleTitle = Placeholder<FlowContent>()
val articleText = Placeholder<FlowContent>()
override fun FlowContent.apply() {
article {
h2 {
insert(articleTitle)
}
p {
insert(articleText)
}
}
}
}
I recommend implementing your own layer on top of kotlinx-html for templating because that's not hard to do and it will solve your particular problem better.

Related

How to await inside a crossinline lambda from a suspend function?

I am using KotlinX.html to emit HTML to an output stream (e.g. FileWriter in the next sample) and the resulting HTML view depends of asynchronous data from a CompletableFuture (e.g. cfArtist).
I want to immediately start emitting static HTML (i.e. <html><body><h1>Artist Info</h1>), then suspend until data is available and then proceed.
HTML builders of KotlinX.html API are inline functions with crossinline functions as parameter, thus I am not allowed to call await in corresponding lambdas (e.g. cfArtist.await()).
I do not want to await before start emitting HTML and do not want to create other coroutines either. Is there any way to await inside those crossinline lambdas?
suspend fun viewArtistInfo(fileName: String, cfArtist: CompletableFuture<Artist>) {
// val artist = cfArtist.await() // Ok
FileWriter(fileName).use {
// val artist = cfArtist.await() // Ok
it
.appendHTML()
.html {
body {
h1 { +"Artist Info" }
val artist = cfArtist.await() // ERROR Suspension functions can be called only within coroutine body
p { +"From: ${artist.from}" }
}
}
}
}

Closing (Auto)Closeables that exist only in `Either`

I currently face the problem of correctly closing resources that never leave their containing Either.
The relevant code looks something like this:
object SomeError
class MyRes : AutoCloseable { [...] }
fun createRes(): Either<SomeError, MyRes> { [...] }
fun extractData(res: MyRes): String { [...] }
fun theProblem(): Either<SomeError, String> {
return createRes()
.map { extractData(it) }
}
What is the most idiomatic way of closing the created MyRes? Closing it before that map prevents extractData from accessing it, and after the map I can't access it anymore via Either's operations. Closing it in extractData severely limits composability.
Currently I have an external List<AutoCloseable> that I iterate over after all the computations, but that can't be the intended way.
I am open to using Arrow Fx (e.g. Resource) if that helps, but I haven't found anything on how to combine Either and Resource in an elegant way.
It's possible to combine the either and Resource safely.
object SomeError
class MyRes : AutoCloseable { [...] }
fun createRes(): Resource<Either<SomeError, MyRes>> { [...] }
fun extractData(res: MyRes): String { [...] }
suspend fun solution(): Either<SomeError, String> = either {
createRes().use { either: Either<SomeError, MyRes> ->
val res = either.bind()
val string = extractData(res)
// call other Either code + `bind()` safely here
[...]
} // <-- MyRes will automatically close here
}
If in this code you encounter Either.Left and you call bind() on it the Resource will first close, because we jump outside of use, and then either will return the encountered Either.Left.
One possible solution I found was wrapping the block passed to map:
fun <B : AutoCloseable, C> andClose(f: (B) -> C): (B) -> C =
{ b: B -> b.use { f(b) } }
fun theProblemSlightlySolved(): Either<SomeError, String> {
return createRes()
.map(andClose { extractData(it) })
}

Is it possible to avoid function names at all in Kotlin DSL?

In Kotlin DSL example they use plus signs to implement raw content inserting:
html {
head {
title {+"XML encoding with Kotlin"}
}
// ...
}
Is it possible to define "nameless" functions in receiver to be able to write
html {
head {
title {"XML encoding with Kotlin"}
}
// ...
}
Are there any plans to do so in future versions of Kotlin?
Is there such things in languages, other than Kotlin?
I can think of two solutions to your problem:
Make the lambda with receiver return a String:
fun title(init: Title.() -> String) {
val t = Title().apply {
children.add(TextElement(init()))
}
children.add(t)
}
You can now call the title as suggested in OP. Actually this seems to be overhead in this particular scenario though and I'd recommend the following.
Create another title method that takes a String directly:
class Head : TagWithText("head") {
fun title(init: Title.() -> Unit) = initTag(Title(), init)
fun title(text: String) {
val t = Title().apply {
children.add(TextElement(text))
}
children.add(t)
}
}
Used like this:
head {
title("XML encoding with Kotlin")
}

How do I extract parts of code into local variables in Kotlin when using Ktor's HTML builder?

I am trying to understand the HTML builder in Kotlin / Ktor.
The example here uses the HTML builder to build the result:
call.respondHtml {
head {
title { +"HTML Application" }
}
body {
h1 { +"Sample application with HTML builders" }
widget {
+"Widgets are just functions"
}
}
}
I am trying to extract the body into a variable like this:
val block: HTML.() -> Unit = {
head {
title { +"HTML Application" }
}
body {
h1 { +"Sample application with HTML builders" }
}
}
call.respondHtml(block)
Now I get the following compile error:
Error:(37, 22) Kotlin: None of the following functions can be called with the arguments supplied:
public suspend fun ApplicationCall.respondHtml(status: HttpStatusCode = ..., versions: List<Version> = ..., cacheControl: CacheControl? = ..., block: HTML.() -> Unit): Unit defined in org.jetbrains.ktor.html
public suspend fun ApplicationCall.respondHtml(status: HttpStatusCode = ..., block: HTML.() -> Unit): Unit defined in org.jetbrains.ktor.html
When I add the first (optional) argument it works again: call.respondHtml(HttpStatusCode.OK, block).
Why does it not work, when I simply try to extract the body into a variable?
I think the compiler doesn't like having a mandatory after default parameters, unless it is a lambda outside of the braces.
Try to name it:
call.respondHtml(block = block)
BTW, if what you want is to extract logic, I would recommend using functions. For your example:
fun HTML.headAndBody() {
head {
title { +"HTML Application" }
}
body {
h1 { +"Sample application with HTML builders" }
widget {
+"Widgets are just functions"
}
}
}
call.respondHtml {
headAndBody()
}
That way you can even add parameters to your block of html, creating a custom component out of it.

How to call a javascript function from Kotlin that expects a jQuery ajax settings object?

I need to convert the following code, it uses something in jQuery called an ajax settings (which is used in the javascript below inside the create() call to create a CouchDB database)
$.couch.db("mydb").create({
success: function(data) {
console.log(data);
},
error: function(status) {
console.log(status);
}
});
I have defined these
#JsName("$")
external val jq: JQuery
val jCouch: dynamic get() = jq.asDynamic().couch
but I am stuck on how to convert the javascript code
jCouch.db.create("mydb").what now?
There are several ways. First, you can create a dynamic object and set required properties there:
val settings: dynamic = Any()
settings.success = { data: dynamic -> console.log(data) }
settings.error = { status: dynamic -> console.log(status) }
jCouch.db("db").create(settings)
(you may also specify corresponding type for data or status)
Second, you can use json function:
jCouch.db("db").create(json(
"success" to { ... }, // edit note: added missing comma
"error" to { ... }
))
Third, you can write typed headers. I don't know what is Couch API, so headers would look approximately like that:
external interface Db {
fun create(settings: DbCreateSettings)
}
external interface DbResult
external interface DbError
external interface DbCreateSettings {
val success: ((DbResult) -> Unit)?
val error: ((DbError) -> Unit)?
}
fun JQuery.db(name: String): Db = asDynamic().db(name)
fun foo() {
jq.db("name").create(object : DbCreateSettings {
override val success = { it: DbResult -> console.log(it) }
override val error = { it: DbError -> console.log(it) }
})
}
Finally, you can see how Kotlin stdlib declares headers for options