Passer au contenu principal

Étendre la DSL

Ouvrir dans ChatGPT

Le Kotlin DSL est extensible, permettant l'ajout de fonctions DSL pour des composants personnalisés ou des bibliothèques tierces. Vous pouvez créer des composants composites qui utilisent le DSL en interne.

Ajouter des composants au DSL​

Pour rendre un composant disponible dans le DSL, créez une fonction d'extension sur HasComponents qui utilise la fonction d'aide init.

Fonction DSL de base​

Voici le modèle pour un composant simple. Cet exemple utilise un composant StarRating personnalisé :

import com.webforj.concern.HasComponents
import com.webforj.kotlin.dsl.WebforjDsl
import com.webforj.kotlin.dsl.init
import com.example.component.StarRating

fun @WebforjDsl HasComponents.starRating(
block: @WebforjDsl StarRating.() -> Unit = {}
): StarRating {
return init(StarRating(), block)
}

La fonction init fait trois choses :

  1. Ajoute le composant au conteneur parent
  2. Exécute le bloc de configuration
  3. Renvoie le composant configuré

Vous pouvez maintenant utiliser le composant dans le code DSL :

div {
starRating {
value = 4
max = 5
}
}

Ajouter des paramètres​

La plupart des fonctions DSL acceptent des paramètres communs avant le bloc de configuration :

fun @WebforjDsl HasComponents.starRating(
value: Int? = null,
max: Int? = null,
block: @WebforjDsl StarRating.() -> Unit = {}
): StarRating {
val rating = StarRating()
value?.let { rating.value = it }
max?.let { rating.max = it }
return init(rating, block)
}

L'utilisation devient plus concise :

div {
starRating(value = 4, max = 5)
starRating(value = 3) {
styles["color"] = "gold"
}
}

Création de composants composites​

Un Composite regroupe plusieurs composants en une seule unité réutilisable. Le DSL fonctionne bien pour définir la structure composite.

Composite de base​

class SearchBox : Composite<Div>() {

private val self = boundComponent
val searchField: TextField
val searchButton: Button

init {
self.apply {
styles["display"] = "flex"
styles["gap"] = "8px"

searchField = textField(placeholder = "Recherche...") {
styles["flex"] = "1"
}

searchButton = button("Rechercher") {
theme = ButtonTheme.PRIMARY
}
}
}

fun onSearch(handler: (String) -> Unit) {
searchButton.onClick {
handler(searchField.text)
}
searchField.onEnter {
handler(searchField.text)
}
}
}

Le composite expose des références de composants pour un accès externe et fournit des méthodes pratiques pour des opérations courantes.

Ajouter un support DSL​

Créez une fonction DSL afin que le composite puisse être utilisé comme des composants intégrés :

fun @WebforjDsl HasComponents.searchBox(
block: @WebforjDsl SearchBox.() -> Unit = {}
): SearchBox {
return init(SearchBox(), block)
}

Il s'intègre maintenant naturellement :

div {
h1("Catalogue de Produits")

searchBox {
onSearch { query ->
filterProducts(query)
}
}

// Liste des produits...
}

Exemples : Indicateur de statut​

Voici un exemple complet d'un composite d'indicateur de statut :

class StatusIndicator : Composite<Div>() {

private val self = boundComponent
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 {
self.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 }
}

// Fonction 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)
}

Utilisation :

div {
statusIndicator("Base de données", StatusIndicator.Status.ACTIVE)
statusIndicator("Cache", StatusIndicator.Status.WARNING)
statusIndicator("API Externe", StatusIndicator.Status.ERROR)
}