Wicket 6.0 – Teil 2 – Validierung von Forms

Und weiter gehts, im letzten Teil haben wir uns grundsätzlich über die neue Backing-Library Funktionalität von Wicket 6 unterhalten und die überfällige JQuery-Integration.

Heute schauen wir mal, ob und wenn ja welche Neuigkeiten Wicket 6 für die Validierung mitbringt.

Hierfür bauen wir eine sehr einfache Form, über die man ein Kunde seine E-Mailadresse ändern kann.

[code language=”html”]

<meta charset="utf-8" />
Apache Wicket Quickstart
<link title="Stylesheet" href="style.css" rel="stylesheet" media="screen" type="text/css" /></pre>
<form>
<input type="text" />
<input type="submit" /></form>
<pre>

[/code]

Der Java-Code sieht so aus:

[code language=”java”]

public class HomePage extends WebPage {
private static final long serialVersionUID = 1L;

public HomePage(final PageParameters parameters) {
Form form = new Form("form", new CompoundPropertyModel(new Email()));
form.add(new TextField("mail"));
form.add(new AjaxSubmitLink("submit") {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
System.out.println(form.getDefaultModelObject());
target.add(HomePage.this);
}

@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
target.add(HomePage.this);
}
});
add(form);
}
}

[/code]

Zusätzlich bauen wir uns noch eine einfache Email-Klasse:

[code language=”java”]

/**
* @author martin.dilger
*/
public class Email implements Serializable {

private String mail;

public Email(){}

public Email(String email){
this.mail = email;
}

public String getMail(){
return mail;
}

@Override
public String toString() {
return mail;
}
}

[/code]

Die Form wird über einen einfache Ajax-Submitlink abgeschickt. Bisher nichts neues, der Code sieht mit Wicket1.5.x  genauso aus.

Schickt man die Form ab, passiert erst mal nichts. Was uns noch fehlt ist die ganze Logik zum Validieren der E-Mailadresse.

Hierfür extrahieren wir die Erzeugung des Email-Textfeldes in eine eigene Factory-Methode:

[code language=”java”]

private TextField emailField() {
TextField mail = new TextField("mail");

[/code]

Das Model der Form ist bereits ein CompoundPropertyModel vom Typ Email, es reicht also, wenn das Textfeld mit der ID “mail” instantiiert wird.

Da wir für die Validierung eine Instanz unserer Email benötigen, braucht das Textfeld zunächst mal einen Konverter (ansonsten könnten wir nur auf einem String arbeiten).

[code language=”java”]

TextField mail = new TextField("mail"){
@Override
public IConverter getConverter(Class type) {
return (IConverter)new AbstractConverter(){
@Override
protected Class getTargetType() {
return Email.class;
}

@Override
public Email convertToObject(String s, Locale locale) {
return new Email(s);
}

@Override
public String convertToString(Email value, Locale locale) {
return value.getMail();
}
};
}
};

[/code]

Auch das unterscheidet sich in keiner Weise von Wicket 1.5.x.

Was wir jetzt aber machen können ist, einen Validator an unsere Komponente zu hängen (ja ich weiß, es gibt einen fertigen EmailAddressValidator..)

[code language=”java”]

mail.add(new IValidator() {
@Override
public void validate(IValidatable validatable) {
Email email = validatable.getValue();
if (!email.isValid()) {
ValidationError validationError = new ValidationError().addKey("email.error");
validationError.setVariable("email", email.getMail());
validatable.error(validationError);
}
}
});

[/code]

Die Klasse AbstractValidator ist mittlerweile deprecated, wir arbeiten also direkt auf IValidator.

Zunächst erzeugen wir eine neue ValidationError-Instanz. ValidationError selbst ist ziemlich mächtig, beispielsweise lädt dieser selbstständig Fehlermeldungen über den registrieren Fehler-Key.

[code language=”java”]

new ValidationError().addKey("email.error");

[/code]

Leider, leider gibt es in der aktuellen Version (Wicket-6.0-beta1) einen Bug, der eine NullPointerException nach sich zieht, wenn hier ein Propertykey angegeben wird, der nicht vorhanden ist. Dieser Bug ist aber mittlerweile gefixt und sollte mit der Wicket-6.0-beta2 ausgerollt werden (die sollte eigentlich heute abend erscheinen).

Zusätzlich hat man die Möglichkeit, Variablen zu registrieren.

[code language=”java”]
validationError.setVariable("email", email.getMail());
[/code]

Man kann dann beispielsweise eine solche Fehlermeldung in einer Propertydatei definieren:

[code]
email.error=Ungueltige Emailadresse : ${email}
[/code]

Der Platzhalter email würde durch die Variable ersetzt werden, was in diesem Fall die eingegebene Emailadresse ist.

FeedbackMessages

Eine interessante Neuigkeit hat sich mit Wicket-6 aber doch noch fast unbemerkt eingeschlichen.
Feedback-Messages (beispielsweise nach einem Konvertierungs- oder Validierungsfehler) wurden bisher immer in der Session gespeichert. Das hat sich grundlegend geändert, denn in Wicket 6 merkt sich jede Komponente selbst, welche Fehler für sie vorhangen sind.

Wie funktioniert das im Code?

[code language=”java”]
public FeedbackMessages getFeedbackMessages()
{
FeedbackMessages messages = getMetaData(FEEDBACK_KEY);
if (messages == null)
{
messages = new FeedbackMessages();
setMetaData(FEEDBACK_KEY, messages);
}
return messages;
}
[/code]

Hierfür hält jede Komponente eine Instanz der Klasse FeedbackMessages.

Wie kam man bisher an alle registrierten FeedbackMessages?

[code language=”java”]

FeedbackMessages messages = Session.get().getFeedbackMessages()

[/code]

Das funktioniert immer noch, würde aber in Wicket-6 nur die Meldungen zurückliefern, die explizit in der Session gespeichert wurden (was beispielsweise dann Sinn macht, wenn Fehlermeldungen über Page-Grenzen hinweg erhalten bleiben sollen).

Wie aber kommt man jetzt an alle Fehlermeldungen, die beispielsweise beim Abschicken einer Form erzeugt wurden?

Hierfür gibt es einen neuen Mechanismus, den FeedbackMessageCollector.

Schauen wir uns das Ganze im Beispiel an:

Wir haben ja bereits ein FeedbackPanel auf der einfachen Seite eingebaut, das einen Fehler anzeigt, wenn eine falsche Emailadresse eingegeben wird.

Wieso funktioniert das? Das zeigt ein einfacher Blick in die neue Implementierung des FeedbackPanels, genauer sogar in der Klasse FeedbackMessagesModel.

[code language=”java”]
messages = new FeedbackCollector(pageResolvingComponent.getPage()).collect(filter);
[/code]

Was passiert intern? Der Collector sucht rekursiv in der übergebenen Komponente (hier die Page) und allen Kind-Komponenten
(und optional auch der Session) nach Meldungen, die registriert sind und zeigt diese an.

Mir gefällt dieser Ansatz ganz gut, da jetzt die Zuständigkeit für das Verwalten von FeedbackMessages von der Session
(die eigentlich mit dieser Thematik überhaupt nichts zu tun haben sollte) in die Komponenten gewandert ist
(wo sie auch hingehört).

Vielleicht noch ein letztes Wort zum Cleanup von FeedbackMessages.

Am Ende jedes Requests werden FeedbackMessages weggeräumt. Die geschieht in der detachFeedback-Methode der Klasse
Component:

[code language=”java”]
private void detachFeedback()
{
FeedbackMessages feedback = getMetaData(FEEDBACK_KEY);
if (feedback != null)
{
final int removed = feedback.clear(getApplication().getApplicationSettings()
.getFeedbackMessageCleanupFilter());

if (feedback.isEmpty())
{
setMetaData(FEEDBACK_KEY, null);
}
else
{
feedback.detach();
}
}
}
[/code]

Recht interessant ist hierbei noch folgende Zeile:

[code language=”java”]
feedback.clear(getApplication().getApplicationSettings()
.getFeedbackMessageCleanupFilter())
[/code]

Der FeedbackMessage-Cleanup-Filter entfernt per Default alle Meldungen, die bereits gerendert wurden.
Das passiert per Default auch in der Session.

Es funktioniert aber weiterhin problemlos, eine FeedbackMessage auf Seite A zu registrieren:

[code language=”java”]
Session.get().error("Fehler!");
setResponsePage(SeiteB.class);
[/code]

und diesen Fehler auf Seite B anzuzeigen, da in diesem Fall die FeedbackMessages noch nicht gerendert wurde und somit
auch nicht am Ende des Requests in der detach()-Phase weggeräumt werden.

Im nächsten Artikel schauen wir uns dann ein wenig Ajax-Neuheiten genauer an.

Die Sourcen zu allen Artikeln kann man sich übrigens hier klonen : git@github.com:dilgerma/wicket-6.0-Playground.git

@Override
public final List<FeedbackMessage> getObject()
{
if (messages == null)
{
// Get filtered messages from page where component lives
messages = new FeedbackCollector(pageResolvingComponent.getPage()).collect(filter);

// Sort the list before returning it
if (sortingComparator != null)
{
Collections.sort(messages, sortingComparator);
}

// Let subclass do any extra processing it wants to on the messages.
// It may want to do something special, such as removing a given
// message under some special condition or perhaps eliminate
// duplicate messages. It could even add a message under certain
// conditions.
messages = processMessages(messages);
}
return messages;
}

Der nächste Teil der Wicket-6 Serie ist hier zu finden.


War dieser Blogeintrag für Sie interessant? Evtl. kann ich noch mehr für Sie tun.

Trainings & Know-How aus der Praxis zu

  • Apache Wicket 1.4.x, 1.5.x, 1.6.x
  • GIT – Best Practices, Einsatz, Methoden
  • Spring
  • Java
  • Scrum & Kanban
  • Agiles Arbeiten
Consulting & Softwareentwicklung

  • Requirements Engineering
  • Qualitätssicherung
  • Software-Entwicklung
  • Architektur
  • Scrum & Kanban

Effective Trainings & Consulting - Martin Dilger



Hat Ihnen dieser Blog-Eintrag gefallen? Ich stelle in diesem Blog Informationen über Tools, Frameworks und Werkzeuge zur Verfügung, die mich produktiver machen. Vielleicht kann ich auch Ihnen helfen, produktiver zu werden.


Ich unterstütze Sie als freier Mitarbeiter bei der Entwicklung von Software-Projekten, Agiler Arbeit sowie Schulungen / Fortbildungen.


Jeden Tag ein bisschen produktiver - ab heute