Extending the DSL
El Kotlin DSL es extensible, lo que permite la adición de funciones DSL para componentes personalizados o bibliotecas de terceros. Puedes construir componentes compuestos que utilicen el DSL internamente.
Agregar componentes al DSL
Para hacer que cualquier componente esté disponible en el DSL, crea una función de extensión en HasComponents que utilice la función auxiliar init.
Función básica de DSL
Aquí está el patrón para un componente simple. Este ejemplo supone que tienes un componente Badge personalizado:
import com.webforj.concern.HasComponents
import com.webforj.kotlin.dsl.WebforjDsl
import com.webforj.kotlin.dsl.init
import com.example.component.Badge
fun @WebforjDsl HasComponents.badge(
block: @WebforjDsl Badge.() -> Unit = {}
): Badge {
return init(Badge(), block)
}
La función init hace tres cosas:
- Agrega el componente al contenedor padre
- Ejecuta el bloque de configuración
- Devuelve el componente configurado
Ahora puedes usar el componente en código DSL:
div {
badge {
text = "Nuevo"
variant = Badge.Variant.PRIMARY
}
}
Agregar parámetros
La mayoría de las funciones DSL aceptan parámetros comunes antes del bloque de configuración:
fun @WebforjDsl HasComponents.badge(
text: String? = null,
variant: Badge.Variant? = null,
block: @WebforjDsl Badge.() -> Unit = {}
): Badge {
val badge = Badge()
text?.let { badge.text = it }
variant?.let { badge.variant = it }
return init(badge, block)
}
El uso se vuelve más conciso:
div {
badge("Nuevo", Badge.Variant.PRIMARY)
badge("Venta") {
styles["font-size"] = "12px"
}
}
Crear componentes compuestos
Un Composite envuelve múltiples componentes en una sola unidad reutilizable. El DSL funciona bien para definir estructuras compuestas.
Compuesto básico
class SearchBox : Composite<Div>() {
val searchField: TextField
val searchButton: Button
init {
boundComponent.apply {
styles["display"] = "flex"
styles["gap"] = "8px"
searchField = textField(placeholder = "Buscar...") {
styles["flex"] = "1"
}
searchButton = button("Buscar") {
theme = ButtonTheme.PRIMARY
}
}
}
fun onSearch(handler: (String) -> Unit) {
searchButton.onClick {
handler(searchField.text)
}
searchField.onEnter {
handler(searchField.text)
}
}
}
El compuesto expone referencias de componentes para acceso externo y proporciona métodos de conveniencia para operaciones comunes.
Agregar soporte para DSL
Crea una función DSL para que el compuesto pueda ser utilizado como componentes integrados:
fun @WebforjDsl HasComponents.searchBox(
block: @WebforjDsl SearchBox.() -> Unit = {}
): SearchBox {
return init(SearchBox(), block)
}
Ahora se integra de manera natural:
div {
h1("Catálogo de productos")
searchBox {
onSearch { query ->
filterProducts(query)
}
}
// Lista de productos...
}
Ejemplo: Indicador de estado
Aquí tienes un ejemplo completo de un compuesto indicador de estado:
class StatusIndicator : Composite<Div>() {
private val dot: Div
private val label: Span
var status: Status = Status.INACTIVE
set(value) {
field = value
updateDisplay()
}
var text: String = ""
set(value) {
field = value
label.text = value
}
init {
boundComponent.apply {
styles["display"] = "flex"
styles["align-items"] = "center"
styles["gap"] = "8px"
dot = div {
styles["width"] = "10px"
styles["height"] = "10px"
styles["border-radius"] = "50%"
styles["background"] = "gray"
}
label = span()
}
updateDisplay()
}
private fun updateDisplay() {
dot.styles["background"] = when (status) {
Status.ACTIVE -> "#22c55e"
Status.WARNING -> "#f59e0b"
Status.ERROR -> "#ef4444"
Status.INACTIVE -> "#9ca3af"
}
}
enum class Status { ACTIVE, WARNING, ERROR, INACTIVE }
}
// Función DSL
fun @WebforjDsl HasComponents.statusIndicator(
text: String? = null,
status: StatusIndicator.Status? = null,
block: @WebforjDsl StatusIndicator.() -> Unit = {}
): StatusIndicator {
val indicator = StatusIndicator()
text?.let { indicator.text = it }
status?.let { indicator.status = it }
return init(indicator, block)
}
Uso:
div {
statusIndicator("Base de datos", StatusIndicator.Status.ACTIVE)
statusIndicator("Cache", StatusIndicator.Status.WARNING)
statusIndicator("API externa", StatusIndicator.Status.ERROR)
}