Components
Managing Complexity
Composable components help manage software complexity by allowing developers to break down a complex problem into smaller, more manageable pieces. Other benefits include reusability, testability, and the ability to reason about a system in isolation.
The Component typealias
We rely on a small amount of syntactic sugar defined in kweb.components.Component:
typealias Component = ElementCreator<*>
And then we can use an extension function on this to create a component:
fun Component.simple(
prompt: String = "Enter Your Name",
name: KVar<String>
) {
div {
h1().text(prompt)
input(type = text).value = name
}
div {
span().text(name.map { "Hello, $it" })
}
}
And we use the component like this:
Kweb(port = 16097) {
doc.body {
simple(name = kvar("World"))
}
}
Components are configured through the extension function parameters, typically through a mixture of:
Parameter Type | For |
---|---|
KVals | Values that can change but not be modified by the component |
KVars | Values that can change or be modified by the component |
ObservableLists | Lists of values that can change or be modified by the component |
Double, String, etc | Values that don't change |
The simplest Component's may have no parameters at all, or just one or two, while the most complex might use a DSL builder.
A more complex example
In this example we create a Component that wraps an <input> element styled using the Bulma CSS framework:
interface BulmaClass {
val cssClassName: String
}
enum class BulmaColor(override val cssClassName: String) : BulmaClass {
PRIMARY("is-primary"), LINK("is-link"),
INFO("is-info"), SUCCESS("is-success"),
WARNING("is-warning"), DANGER("is-danger")
}
enum class BulmaSize(override val cssClassName: String) : BulmaClass {
SMALL("is-small"), NORMAL("is-normal"),
MEDIUM("is-medium"), LARGE("is-large")
}
enum class BulmaStyle(override val cssClassName: String) : BulmaClass {
ROUNDED("is-rounded"),
}
enum class BulmaState(override val cssClassName: String) : BulmaClass {
NORMAL("is-normal"), HOVER("is-hovered"),
FOCUS("is-focused"), LOADING("is-loading"),
}
fun Component.bulmaInput(
type: InputType,
value: KVar<String>,
vararg classes : KVal<out BulmaClass> = arrayOf(),
) {
input(type = type) { inputElement ->
var inputClassList: KVal<List<String>> = kval(listOf("input"))
for (c in classes) {
inputClassList += c.map { listOf(it.cssClassName) }
}
with(inputElement) {
classes(inputClassList.map { it.joinToString(" ") })
this.value = value
}
}
}
This component can then be used like this:
val bulmaUrl = "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css".json
fun shouldWarn(username : String) =
if (username.length < 5)
BulmaColor.WARNING
else
BulmaColor.SUCCESS
Kweb(port = 12354) {
doc.head {
element(
"link",
attributes = mapOf(
"rel" to "stylesheet".json,
"href" to bulmaUrl
)
)
}
doc.body {
val username = kvar("")
val color = username.map { shouldWarn(it) }
bulmaInput(type = text, value = username, color)
}
}