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

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.

Related

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

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.

Which are the advantatges of using Template in Ktor in HTML DSL?

I've just started learning Ktor, and I really like the use of the HTML DSL. Reading the docs (https://ktor.io/docs/html-dsl.html#templates) I've seen that there is a Templates engine. However, either there is something that I can't see, or I don't find the advantages of using it. Isn't cleaner and easier to use standard Kotlin extension functions, like the Div, P,... instead?
I post an example of how I would write the example on the tutorial using functions (the template example can be found here):
fun Application.configureRouting() {
configureArticleRouting()
routing {
get("/") {
val name = "Ktor"
call.respondHtml(HttpStatusCode.OK) {
mainLayout(title = name){
articleContent(Article("Article Title", "My article content"))
}
}
}
}
}
fun FlowContent.articleContent(article: Article) {
article {
h2 {
+article.title
}
p {
+article.content
}
}
}
fun HTML.mainLayout(title: String, content: FlowContent.() -> Unit) {
head {
title {
+title
}
}
body {
content()
}
}
data class Article(val title: String, val content: String)
The question is, what is the advantage of using templates?
Thanks,

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 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

How do I write to a file in Kotlin?

I can't seem to find this question yet, but what is the simplest, most-idiomatic way of opening/creating a file, writing to it, and then closing it? Looking at the kotlin.io reference and the Java documentation I managed to get this:
fun write() {
val writer = PrintWriter("file.txt") // java.io.PrintWriter
for ((member, originalInput) in history) { // history: Map<Member, String>
writer.append("$member, $originalInput\n")
}
writer.close()
}
This works, but I was wondering if there was a "proper" Kotlin way of doing this?
A bit more idiomatic. For PrintWriter, this example:
File("somefile.txt").printWriter().use { out ->
history.forEach {
out.println("${it.key}, ${it.value}")
}
}
The for loop, or forEach depends on your style. No reason to use append(x) since that is basically write(x.toString()) and you already give it a string. And println(x) basically does write(x) after converting a null to "null". And println() does the correct line ending.
If you are using data classes of Kotlin, they can already be output because they have a nice toString() method already.
Also, in this case if you wanted to use BufferedWriter it would produce the same results:
File("somefile.txt").bufferedWriter().use { out ->
history.forEach {
out.write("${it.key}, ${it.value}\n")
}
}
Also you can use out.newLine() instead of \n if you want it to be correct for the current operating system in which it is running. And if you were doing that all the time, you would likely create an extension function:
fun BufferedWriter.writeLn(line: String) {
this.write(line)
this.newLine()
}
And then use that instead:
File("somefile.txt").bufferedWriter().use { out ->
history.forEach {
out.writeLn("${it.key}, ${it.value}")
}
}
And that's how Kotlin rolls. Change things in API's to make them how you want them to be.
Wildly different flavours for this are in another answer: https://stackoverflow.com/a/35462184/3679676
Other fun variations so you can see the power of Kotlin:
A quick version by creating the string to write all at once:
File("somefile.txt").writeText(history.entries.joinToString("\n") { "${it.key}, ${it.value}" })
// or just use the toString() method without transform:
File("somefile.txt").writeText(x.entries.joinToString("\n"))
Or assuming you might do other functional things like filter lines or take only the first 100, etc. You could go this route:
File("somefile.txt").printWriter().use { out ->
history.map { "${it.key}, ${it.value}" }
.filter { ... }
.take(100)
.forEach { out.println(it) }
}
Or given an Iterable, allow writing it to a file using a transform to a string, by creating extension functions (similar to writeText() version above, but streams the content instead of materializing a big string first):
fun <T: Any> Iterable<T>.toFile(output: File, transform: (T)->String = {it.toString()}) {
output.bufferedWriter().use { out ->
this.map(transform).forEach { out.write(it); out.newLine() }
}
}
fun <T: Any> Iterable<T>.toFile(outputFilename: String, transform: (T)->String = {it.toString()}) {
this.toFile(File(outputFilename), transform)
}
used as any of these:
history.entries.toFile(File("somefile.txt")) { "${it.key}, ${it.value}" }
history.entries.toFile("somefile.txt") { "${it.key}, ${it.value}" }
or use default toString() on each item:
history.entries.toFile(File("somefile.txt"))
history.entries.toFile("somefile.txt")
Or given a File, allow filling it from an Iterable, by creating this extension function:
fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
this.bufferedWriter().use { out ->
things.map(transform).forEach { out.write(it); out.newLine() }
}
}
with usage of:
File("somefile.txt").fillWith(history.entries) { "${it.key}, ${it.value}" }
or use default toString() on each item:
File("somefile.txt").fillWith(history.entries)
which if you had the other toFile extension already, you could rewrite having one extension call the other:
fun <T: Any> File.fillWith(things: Iterable<T>, transform: (T)->String = {it.toString()}) {
things.toFile(this, transform)
}
It mostly looks ok to me. The only thing different I would do is use the "use" extension defined in ReadWrite to auto close the writer.
PrintWriter("file.txt").use {
for ((member, originalInput) in history) { // history: Map<Member, String>
it.append("$member, $originalInput\n")
}
}
At the very minimum, you could use:
FileWriter(filename).use { it.write(text) }
FileWriter is a convenience class for writing character files (provided by Java, and hence available in Kotlin). It extends Closeable, and hence can be used by Kotlin's ".use" extension method.
The .use extension method automatically closes the calling object once the block exits, thus providing an idiomatic way to close the file after it's written.
Some Kotlin magic allows to omit referencing the stream on each read or write call:
fun <T : Closeable, R> T.useWith(block: T.() -> R): R = use { with(it, block) }
File("a.in").bufferedReader().useWith {
File("a.out").printWriter().useWith {
val (a, b) = readLine()!!.split(' ').map(String::toInt)
println(a + b)
}
}
Scanner(File("b.in")).useWith {
PrintWriter("b.out").useWith {
val a = nextInt()
val b = nextInt()
println(a + b)
}
}
try{
val fileWriter = FileWriter("test.txt", true)
fileWriter.write(string+ "\n")
fileWriter.close()
} catch (exception: Exception){
println(exception.message)
}
Example as easy
val path = context!!.filesDir.absolutePath // => /data/user/0/com.example.test/files
File("$path/filename.txt").writeText("hello")
File(requireContext().filesDir, "TodayTaskListChange.txt").writeText("write your test here...")