Heute mal ein kleiner Ausflug in das Thema der
Validierung.
An mehreren Stellen des Editors möchte ich überprüfen, ob die Eingaben des Benutzers valide sind. Beispiel: Beim Erstellen eines neuen Levels öffnet sich ein Modal mit einem Texteingabefeld, in das der Name des Levels eingetragen werden muss. Wenn man auf den OK-Button klickt, möchte ich sicherstellen, dass das Eingabefeld nicht leer ist und noch kein Level mit dem angegebenen Namen existiert. Außerdem sollen entsprechende Fehlermeldungen angezeigt werden, damit man als Benutzer versteht, was überhaupt los ist.
Allerdings ist Validierung ein großes Thema, für das es eigene, spezialisierte Libraries gibt. Ein so großes Fass wollte ich jedoch gar nicht aufmachen. Deswegen habe ich überlegt, wie ich das Problem möglichst unkompliziert lösen könnte:
- Die Validierung soll auf den Inhalt von Validation-Containern beschränkt sein, vergleichbar mit dem <form>-Element von HTML.
- Widgets können ein onValidate-Callback festlegen, das im Falle einer fehlgeschlagenen Validierung die Fehlermeldung als String zurückgibt.
- Der Validation-Container validiert rekursiv seine Kinder und sammelt die Fehlermeldungen.
- Die Fehlermeldungen können mithilfe von Label-Widgets und reaktiven Data-Bindings angezeigt werden – diese gibt es bereits im GUI.
1. Der Validation-Container
Als Validation-Container kann jedes beliebige Widget dienen, das andere Widgets enthalten kann, im folgenden Fall ein Column-Layout:
Mit
data werden reaktive Properties definiert. Widgets können Bindings zu diesen Properties herstellen und dadurch auf Werteänderungen reagieren. Die
errors-Property soll hier dazu dienen, die gesammelten Fehlermeldungen aus der Validierung zu speichern.
2. onValidate-Callbacks
Die Validierung für das Eingabefeld des Levelnamens:
Code: Alles auswählen
TextInput {
id = "levelNameInput",
onValidate = function (widget)
local levelName = stringx.trim(widget:getValue())
if levelName == "" then
return "Level name is required"
end
if levelutils.doesLevelExist(levelName) then
return "Level already exists"
end
end
}
3. Fehlermeldungen
Im Validation-Container habe ich die
errors-Property zum Sammeln der Fehlermeldungen definiert. Nun kommen die bereits erwähnten Bindings zum Einsatz: Das Label, das für die Anzeige der Eingabefeld-Fehlermeldungen herangezogen wird, soll den Fehlertext aus
errors übernehmen und natürlich nur dann sichtbar sein, wenn es eine Fehlermeldung zum Anzeigen gibt.
Code: Alles auswählen
Label {
styleName = "error-text",
bindings = {
visible = {
source = "errors",
transform = function (_, errors)
return errors["levelNameInput"] ~= nil
end,
},
text = {
source = "errors",
transform = function (_, errors)
return errors["levelNameInput"] or ""
end,
},
}
}
4. Letzter Schritt
Jetzt braucht es nur noch einen Button, der die Validierung auslöst:
Code: Alles auswählen
PushButton {
onClick = function (widget)
if widget:checkValidity() then
-- Validierung erfolgreich; Level erstellen :)
end
end,
Label {
text = "Create",
},
}
Fertig! :)
Jonathan hat geschrieben: ↑13.08.2025, 06:06
Ich hab für den Landvogt die GUI auch aus versehen selber gemacht. Erst war sie super minimalistisch, es gab bloß Buttons aus Bildern, dann kam eine Kleinigkeit nach der anderen hinzu und dann war ich an dem Punkt wo ich nicht mehr alles wegschmeißen und durch ein anderes Framework ersetzen wollte. Es ist immer noch alles recht simpel, es gibt nichtmal das Konzept des Widget-Fokus, d.h. Tastatursteuerung ist unmöglich, von Texteingabe ganz zu schweigen (nagut, es gibt einen Hack für wenn man genau ein Eingabefeld hat, etwa um sich in die Highscore einzutragen).
Das GUI aus Versehen selber gemacht – das ist gut. :D
Manchmal werden die einfachen Zwischenlösungen zu etablierten Dauerlösungen.
Ein vollständiges Fokusmanagement habe ich übrigens auch noch nicht. Ich habe das bisher nur soweit umgesetzt, wie es für die Eingabefelder erforderlich war, d.h. beim Anklicken eines fokussierbaren Widgets erhält dieses den Tastaturfokus. Wenn man außerhalb des Widgets klickt oder das Widget nicht mehr fokussierbar ist (z.B. weil es entfernt oder ausgeblendet wurde), wird der Fokus wieder entfernt. Es ist noch nicht möglich, per Tastatur zum vorherigen oder nächsten Widget zu navigieren.
Ich weiß auch noch nicht, wie ich den ursprünglichen Fokus nach dem Öffnen und Schließen eines Modals wiederherstellen werde. Optimalerweise sollt dann wieder der Button fokussiert werden, der das Modal geöffnet hat. Vielleicht kann ich das ganz naiv mit einer Fokus-Historie lösen... Mal sehen.