Skip to content

Instantly share code, notes, and snippets.

@joost-klitsie
Last active July 22, 2023 12:06
Show Gist options
  • Save joost-klitsie/8959187d61cd5d1a388a2937c318dc13 to your computer and use it in GitHub Desktop.
Save joost-klitsie/8959187d61cd5d1a388a2937c318dc13 to your computer and use it in GitHub Desktop.
Kotlin HTML Builder
fun HtmlTag.Builder.addAttribute(attribute: Pair<String, String>) {
attributes = attributes.plus(attribute)
}
fun HtmlTag.Builder.addChild(child: HtmlElement) {
children = children.plus(child)
}
fun interface HtmlElement {
fun render(indent: String): String
}
fun HtmlTag.Builder.a(
href: String,
configure: HtmlTag.Builder.() -> Unit
) = tag("a") {
addAttribute("href" to href)
configure()
}
fun HtmlTag.Builder.div(
configure: HtmlTag.Builder.() -> Unit
) = tag("div", configure)
fun HtmlTag.Builder.br() = tag("br") {}
fun HtmlTag.Builder.p(
configure: HtmlTag.Builder.() -> Unit
) = tag("p", configure)
fun HtmlTag.Builder.classes(
classNames: String
) = addAttribute("class" to classNames)
<html>
Hello people!
<div>
<a href="http://adnovum.com">
Go to Adnovum!
</a>
</div>
<div>
<p class="my-class">
I am a paragraph
</p>
<br/>
<div class="other-class">
<p>
I am also a paragraph
</p>
</div>
</div>
<div>
number 0
</div>
<div>
number 1
</div>
<div>
number 2
</div>
<div>
number 3
</div>
<div>
number 4
</div>
</html>
val html = html {
text("Hello people!")
div {
a("http://adnovum.com") {
text("Go to Adnovum!")
}
}
div {
p {
classes("my-class")
text("I am a paragraph")
}
br()
div {
classes("other-class")
p {
text("I am also a paragraph")
}
}
}
repeat(5) { index ->
div {
text("number $index")
}
}
}
println(html.render(""))
class HtmlTag private constructor(
val name: String,
val attributes: Map<String, String>,
val children: List<HtmlElement>,
) : HtmlElement {
override fun render(indent: String): String {
val attributeString =
attributes.entries.joinToString(separator = " ") { (key, value) ->
"$key=\"$value\""
}
return if (children.isEmpty()) {
"$indent<$name $attributeString/>"
} else {
val childrenString = children.joinToString("\n") {
it.render("$indent ")
}
"""
|$indent<$name $attributeString>
|$childrenString
|$indent</$name>
""".trimMargin()
}
}
class Builder(
var name: String,
var attributes: Map<String, String> = emptyMap(),
var children: List<HtmlElement> = emptyList(),
) {
fun build() = HtmlTag(name, attributes, children)
}
}
fun html(configure: HtmlTag.Builder.() -> Unit): HtmlElement {
val builder = HtmlTag.Builder("html")
builder.configure()
return builder.build()
}
val html = html { }
println(html.render("")) // prints: <html />
fun HtmlTag.Builder.tag(
name: String,
configure: HtmlTag.Builder.() -> Unit
) = addChild(HtmlTag.Builder(name).apply(configure).build())
val html = html {
tag("p") {
text("Hello, world!")
}
}
println(html.render(""))
/* Output:
<html>
<p>
Hello, world!
</p>
</html>
*/
val html = html {
text("Hello, world!")
}
println(html.render(""))
/* Output:
<html>
Hello, world!
</html>
*/
<TagName attribute="value">children</TagName>
fun HtmlTag.Builder.text(text: String) = addChild { indent -> "$indent$text" }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment