-
Notifications
You must be signed in to change notification settings - Fork 133
Micro templating and DSL customizing
It is very likely that your page may have repeated parts, for example "components" or containers that is typical for your application and you don't want to copy-paste it. The most beginning solution is to extract such things to functions.
For example, consider example with Twitter Bootstrap with dropdowns:
li {
classes = setOf("dropdown")
a("#", null) {
classes = setOf("dropdown-toggle")
attributes["data-toggle"] = "dropdown"
role = "button"
attributes["aria-expanded"] = "false"
ul {
classes = setOf("dropdown-menu")
role = "menu"
li { a("#") { +"Action" } }
li { a("#") { +"Another action" } }
li { a("#") { +"Something else here" } }
li { classes = setOf("divider")}
li { classes = setOf("dropdown-header"); +"Nav header" }
li { a("#") { +"Separated link" } }
li { a("#") { +"One more separated link" } }
}
span {
classes = setOf("caret")
}
}
}
Looks not so simple as should be, isn't it? To get it look better we can introduce some extension functions to extend DSL
Let's introduce top level function dropdown
like this:
fun UL.dropdown(block : LI.() -> Unit) {
li("dropdown") {
block()
}
}
As you can see the function dropdown
can be called on <UL>
and consumes lambda with this
of type LI
. So content inside this lambda will be executed inside <LI>
. Inside we construct li with class dropdown
and call lambda inside it.
We introduce more extension functions to be able to shortcut even more code
fun LI.dropdownToggle(block : A.() -> Unit) {
a("#", null, "dropdown-toggle") {
attributes["data-toggle"] = "dropdown"
role = "button"
attributes["aria-expanded"] = "false"
block()
span {
classes = setOf("caret")
}
}
}
fun LI.dropdownMenu(block : UL.() -> Unit) : Unit = ul("dropdown-menu") {
role = "menu"
block()
}
fun UL.dropdownHeader(text : String) : Unit = li { classes = setOf("dropdown-header"); +text }
fun UL.divider() : Unit = li { classes = setOf("divider")}
After that we can create dropdowns easier
createHTML().ul {
dropdown {
dropdownToggle { +"Dropdown" }
dropdownMenu {
li { a("#") { +"Action" } }
li { a("#") { +"Another action" } }
li { a("#") { +"Something else here" } }
divider()
dropdownHeader("Nav header")
li { a("#") { +"Separated link" } }
li { a("#") { +"One more separated link" } }
}
}
It looks much more clear and it is easier to understand and modify.
There are cases when you need something missing then there are two options: file bug and wait or write your own.
First of all we have to define a tag type
class CUSTOM(consumer: TagConsumer<*>) :
HTMLTag("custom", consumer, emptyMap(),
inlineTag = true,
emptyTag = false), HtmlInlineTag {
}
For example we know that it is applicable to <div>
only so let's declare it
fun DIV.custom(block: CUSTOM.() -> Unit = {}) {
CUSTOM(consumer).visit(block)
}
If you want it to be available on the root (like this: appendHTML().custom { }
) you have to declare it on TagConsumer
:
fun <T> TagConsumer<T>.custom(block: CUSTOM.() -> Unit = {}): T {
return CUSTOM(this).visitAndFinalize(this, block)
}
So now let's see how it works
// inside div
buildString {
appendHTML(false).div {
custom {
span { +"content" }
}
}
}
// on the root
buildString {
appendHTML(false).custom {
span {
+"content"
}
}
}