Zum Hauptinhalt springen

Extending the DSL

In ChatGPT öffnen

Die Kotlin DSL ist erweiterbar und ermöglicht das Hinzufügen von DSL-Funktionen für benutzerdefinierte Komponenten oder Bibliotheken von Drittanbietern. Sie können zusammengesetzte Komponenten erstellen, die die DSL intern verwenden.

Komponenten zur DSL hinzufügen

Um eine Komponente in der DSL verfügbar zu machen, erstellen Sie eine Erweiterungsfunktion für HasComponents, die die Hilfsfunktion init verwendet.

Grundlegende DSL-Funktion

Hier ist das Muster für eine einfache Komponente. Dieses Beispiel verwendet eine benutzerdefinierte StarRating-Komponente:

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

Die init-Funktion macht drei Dinge:

  1. Fügt die Komponente dem übergeordneten Container hinzu
  2. Führt den Konfigurationsblock aus
  3. Gibt die konfigurierte Komponente zurück

Jetzt können Sie die Komponente im DSL-Code verwenden:

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

Parameter hinzufügen

Die meisten DSL-Funktionen akzeptieren gängige Parameter vor dem Konfigurationsblock:

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

Die Verwendung wird damit prägnanter:

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

Zusammengesetzte Komponenten erstellen

Ein Composite fasst mehrere Komponenten in einer einzigen wiederverwendbaren Einheit zusammen. Die DSL eignet sich gut zur Definition der zusammengesetzten Struktur.

Grundlegendes Composite

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 = "Suche...") {
styles["flex"] = "1"
}

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

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

Das Composite stellt Komponentenreferenzen für den externen Zugriff bereit und bietet Hilfsmethoden für häufige Operationen.

DSL-Unterstützung hinzufügen

Erstellen Sie eine DSL-Funktion, damit das Composite wie eingebaute Komponenten verwendet werden kann:

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

Jetzt integriert es sich natürlich:

div {
h1("Produktkatalog")

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

// Produktliste...
}

Beispiel: Statusindikator

Hier ist ein komplettes Beispiel für ein Composite des Statusindikators:

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"] = "grau"
}

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 }
}

// DSL-Funktion
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)
}

Verwendung:

div {
statusIndicator("Datenbank", StatusIndicator.Status.ACTIVE)
statusIndicator("Cache", StatusIndicator.Status.WARNING)
statusIndicator("Externe API", StatusIndicator.Status.ERROR)
}