You already know how to generate HTML for the user with the FreeMarker template engine. Now let's get acquainted with Ktor's built-in tool for working with HTML: Kotlin HTML DSL.
Getting started
HTML DSL is in fact a special language that makes it easy to specify HTML directly in your application code without using templating tools.
To make HTML DSL available in your project you need to include a dependency in the build.gradle.kts:
implementation("io.ktor:ktor-server-html-builder-jvm:$ktor_version")Then import it in the code file:
import io.ktor.server.html.*
import kotlinx.html.*
fun Application.configureRouting() {
routing {
get("/html") {
}
}
}Great! Now we can start working with the HTML DSL.
Send HTML in response
We can use the call.respondHtml function to generate and return HTML to the user. This function takes a template of HTML code written in the HTML DSL language as an input. With this language, we can specify any nesting of tags and substitute values in the template with the + symbol. The easiest way to understand this is to see a concrete example:
call.respondHtml(HttpStatusCode.OK) {
head {
title {
+"html page title"
}
}
body {
h1 {
+"Hello, user!"
}
}
}This piece of code returns HTML of the following form:
<html>
<head>
<title>html page title</title>
</head>
<body>
<h1>Hello, user!</h1>
</body>
</html>In the browser:
HTML DSL allows to substitute in a template not only specific string values but also values from variables. We use the + symbol for this as well:
val text = "Hello, user!"
call.respondHtml(HttpStatusCode.OK) {
body {
+text
}
}Output:
<html>
<body>Hello, user!</body>
</html>
Embedding a loop
The next important thing you need to know is how to embed the loop in the template. You rarely know how many tags you will have to display. Imagine you have a site where users can leave comments. You don't know in advance how many comments you have to display.
Let's say you have a MutableList, which contains the current comments you want to output. To do that, you just need to wrap the repeating part of the pattern in a for loop:
val comments = mutableListOf("Hello, everyone!", "Great site", "Nice :)")
call.respondHtml(HttpStatusCode.OK) {
body {
ul {
for(comment in comments) {
li {
+comment
}
}
}
}
}Output:
<html>
<body>
<ul>
<li>Hello, everyone!</li>
<li>Great site</li>
<li>Nice :)</li>
</ul>
</body>
</html>
HTML DSL Templates
As you can see, Ktor gives you a great opportunity to construct HTML directly in routing. This can be convenient when you need to output small HTML page quickly, but in large projects, this approach can seriously reduce the readability of the code. Therefore, when working with large HTML pages, we recommend putting them in a separate file. HTML DSL has its own templating engine for this purpose.
To create a template, we need to define a class that inherits from Template<HTML> and override its HTML.apply() method, in which we put our HTML code in HTML DSL.
class MyTemplate : Template<HTML> {
override fun HTML.apply() {
body {
h1 {
+"Hello, user!"
}
}
}
}Next, we can use this template in routing using the call.respondHtmlTemplate function.
get("/html") {
call.respondHtmlTemplate(MyTemplate()) {}
}Now, when we access /html we get the following output:
<html>
<body>
<h1>Hello, user!</h1>
</body>
</html>We got the same result as if we had defined HTML directly in the routing as in the previous section.
You may have wondered why you need empty curly braces after calling the call.respondHtmlTemplate function. In fact, HTML DSL templates allow you to pass parameters inside themselves. These parameters are passed in curly braces after calling the call.respondHtmlTemplate. If the template does not accept parameters, the brackets remain empty.
In the next section, we'll look at how to pass parameters into our template.
Passing parameters to the template
In the example above, our template simply displayed the message "Hello user!". What if we want to greet someone else?
Let's take the name of whom we want to greet in the parameters. Then when we call call.respondHtmlTemplate we can pass any name to the template.
class MyTemplate() : Template<HTML> {
val name = Placeholder<FlowContent>()
override fun HTML.apply() {
body {
h1 {
+"Hello "
insert(name)
}
}
}
}As you can see, we set the name parameter with the Placeholder<FlowContent> construction . Then we use the insert(name) command to specify the place in HTML where the passed value will be inserted.
Now we can pass our name into the template:
call.respondHtmlTemplate(MyTemplate()) {
name {
+"world!"
}
}Result:
Try to pass in a different name and you'll see how the welcome message changes.
Child templates
In fact, we can pass not only the parameters to the template but also the other templates.
Templates that can be passed as parameters to other templates are called child templates. They must inherit from Template<FlowContent> and override FlowContent.apply()
class ChildTemplate() : Template<FlowContent> {
override fun FlowContent.apply() {
hr { }
div {
+"Child template"
}
hr { }
}
}Let's pass this template to our good old MyTemplate.
class MyTemplate() : Template<HTML> {
val childTemplate = TemplatePlaceholder<ChildTemplate>()
override fun HTML.apply() {
body {
h1 {
+"Hello "
insert(ChildTemplate(), childTemplate)
}
}
}
}To specify that we want to get ChildTemplate in parameters, we use the TemplatePlaceholder<ChildTemplate>() function. With insert(ChildTemplate(), childTemplate) we specify the place in HTML where to insert the content of the child template.
Now, in routing, let's return MyTemplate and pass ChildTemplate as a parameter to it.
call.respondHtmlTemplate(MyTemplate()) {
childTemplate {}
}Let's open the browser and see our new result:
<html>
<body>
<h1>Hello
<hr>
<div>Child template</div>
<hr>
</h1>
</body>
</html>Ktor inserted a child template where we wanted it.
For more information about HTML DSL you can refer to the official ktor documentation.
Conclusion
In this topic, we introduced the HTML DSL.
We learned how to construct HTML directly in routing, and how to use loops to display lists.
We also learned how to create HTML DSL templates, which can accept different parameters or child templates.
Don't worry if you don't remember something from this topic. You can always go back to the theory. Now let's put what we've learned into practice.