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 TypeFor
KValsValues that can change but not be modified by the component
KVarsValues that can change or be modified by the component
ObservableListsLists of values that can change or be modified by the component
Double, String, etcValues 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)
    }
}