Skip to content


Wicket Ajax Tipp - AjaxRequestTarget nachträglich ermitteln

Gestern wurde ich gefragt, ob und wie man denn vermeiden kann, dass man das AjaxRequestTarget immer als Funktionsargument weiterreichen muss, wenn man an anderer Stelle darauf zugreifen möchte. Ich muss zugeben, dass ich die einfache Antwort nicht sofort wusste (was, wie man sehen wird, erstaunlich ist). Ich wusste aber, dass mir kürzlich ein Stück Wicket-Code begegnet ist, wo ermittelt werden musste, ob ein AjaxRequest oder ein normaler Request abgearbeitet wird.

Ich machte mich also erneut auf die Suche und fand die Code-Zeile in der AjaxFallbackButton-Klasse. Ich war kurz davor ein paar Zeilen Code daraus abzuleiten und warf nur einen flüchtigen Code in die AjaxRequestTarget-Klasse, um festzustellen, das es das natürlich schon gibt.

AjaxRequestTarget target=AjaxRequestTarget.get();

So einfach geht das. So einfach, dass ich es fast übersehen hätte.

Tags:

Veröffentlicht in Allgemein, Wicket, .

Wicket Form Submit - mit und ohne Ajax

Webanwendungen ohne Formulare gibt es nicht. Dabei ist die korrekte Formularbehandlung alles andere als trivial. Zum Glück bietet Wicket eine ausgezeichnete Unterstützung für Formulare, die kaum Wünsche offen lässt. Doch auch mit Wicket sind ein paar Dinge zu beachten, um die verschiedenen Interaktionsmöglichkeiten des Nutzers korrekt zu verarbeiten.

Wicket bietet die Möglichkeit, neben dem normalen Abschicken eines Formulars auch auf die angeklickten Submit-Buttons reagieren zu können. Damit man aber auch zuverlässig unterscheiden kann, ob der Nutzer das Formular durch ein Enter im Textfeld oder durch einen Klick auf einen Button abgeschickt hat, muss man ein paar Vorbereitungen treffen.

Für das Formular legen wir eine Bean an, welche die Daten aufnimmt.

package de.wicketpraxis.web.blog.pages.questions.form.submit;

import java.io.Serializable;

public class FormBean implements Serializable
{
  String _name;
  
  public String getName()
  {
    return _name;
  }
  
  public void setName(String name)
  {
    _name = name;
  }
}

Für unser Beispiel benötigen wir ein Formular, einen AjaxFallbackButton, einen normalen Button und einen Button, den wir dazu benutzen, denn Fall herauszubekommen, wenn der Nutzer einfach nur Enter gedrückt hat. Das FeedbackPanel sollte man auch nicht vergessen.

package de.wicketpraxis.web.blog.pages.questions.form.submit;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxFallbackButton;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;

public class FormSubmitPage extends WebPage
{
  int _counter;
  
  public FormSubmitPage()
  {
    final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback");
    feedbackPanel.setOutputMarkupId(true);
    add(feedbackPanel);
    
    CompoundPropertyModel<FormBean> formModel = new CompoundPropertyModel<FormBean>(new FormBean());
    Form<FormBean> form = new Form<FormBean>("form",formModel)
    {
      @Override
      protected void onSubmit()
      {
        info("Form Submit: "+_counter++);
      }
    };
    
    form.add(new TextField<String>("name"));
    
    form.add(new Button("defaultSubmit")
    {
      @Override
      public void onSubmit()
      {
        info("Default Button: "+_counter++);
      }
    });
    
    form.add(new Button("submit")
    {
      @Override
      public void onSubmit()
      {
        info("Button: "+_counter++);
      }
    });
    
    form.add(new AjaxFallbackButton("ajaxSubmit",form)
    {
      @Override
      protected void onSubmit(AjaxRequestTarget target, Form<?> form)
      {
        if (target!=null)
        {
          info("AjaxSubmit Button(ajax): "+_counter++);
          target.addComponent(feedbackPanel);
        
          _counter=0;
        }
        else
        {
          info("AjaxFallbackButton: "+_counter++);
        }
      }
    });
    
    add(form);
  }
  
  @Override
  protected void onBeforeRender()
  {
    _counter=0;
    super.onBeforeRender();
  }
}

Damit man die Reihenfolge besser erkennen kann, mit der die onSubmit-Methoden aufgerufen werden, wird ein Zähler (_counter) hochgezählt und vor dem Darstellen des Formulars wieder zurückgesetzt. In diesem Beispiel liegt die entscheidende Information im Markup. Dabei ist die Reihenfolge der verschiedenen Submit-Komponenten wichtig.

<html>
  <head>
    <title>FormSubmitPage</title>
  </head>
  <body>
    <div wicket:id="feedback"></div>
    
    <form wicket:id="form">
      <input wicket:id="defaultSubmit" type="submit" value="" style="border:0px; width: 0px; height: 0px; visibility: collapse; display: compact;">
      Name <input wicket:id="name"><br>
      <input wicket:id="ajaxSubmit"  type="submit" value="AjaxButton">
      <input wicket:id="submit" type="submit" value="Button"> 
    </form>
  </body>
</html>

Der Button mit der Wicket-ID defaultSubmit muss vor allen anderen Submit-Komponenten stehen. Dadurch wird beim Abschicken des Formulars durch Enter dieser Button ausgewählt. Wenn einer der anderen Buttons angeklickt wird, dann wird das Formular über diesen Button abgeschickt. Doch warum soviel Aufwand? Die onSubmit()-Methode des Formulars wird doch in jedem Fall aufgerufen.

Der Aufwand ist notwendig, wenn man abhängig davon, ob der Nutzer auf einen Button oder eben auf keinen Button geklickt hat, eine Aktion ausführen möchte. Das bedeutet, dass man die Aktion nicht in onSubmit ausführen kann. Es ist allerdings auch nicht möglich, im onSubmit() der Buttonkomponenten ein Flag zu setzen, dass dann in der onSubmit()-Methode des Formulars aufgerufen wird, da die Methode beim AjaxFallbackButton erst nach dem onSubmit() der Komponente aufgerufen wird.

Folgende Ergebnisse erhält man, wenn man das Formular a) per Enter, b) per AjaxButton und c) per Button abschickt:

Submit per Enter

Submit per AjaxButton

Submit per Button

Wenn Javascript deaktiviert ist, dann erhält man folgende Ausgabe: Submit per Enter

Submit per AjaxButton

Submit per Button

Wenn man dieses Vorgehen beherzigt, funktioniert die Anwendung auch mit deaktiviertem JavaScript genauso zuverlässig. Das freut den Nutzer und in diesem Fall auch den Entwickler:)

Tags:

Veröffentlicht in Technologie, Wicket, .

Praxisbuch Wicket - Übersicht

Hinterher ist man meistens schlauer, hofft man. Nachdem ich gerade per Twitter auf eine JSF-Mindmap gestoßen bin (dank @ptrthomas), dachte ich mir, dass es hilfreich sein könnte, die Mindmap, die ich mir bei der Arbeit am Buch erstellt habe, auch anderen zur Verfügung zu stellen. Perfektionismus führt manchmal dazu, dass man nie ankommt, daher hier sofort das ungefilterte Ergebnis.

Wicket Struktur - Übersicht aus der Arbeit am Buch

Tags:

Veröffentlicht in Allgemein, JSF, Wicket, .

E-Mail mit Wicket verschicken

Wäre es nicht praktisch, wenn man eine E-Mail verschicken könnte, in dem man die Komponenten, die man für die Darstellung auf der Webseite verwendet, auch für das Erstellen der E-Mail heranziehen könnte. Das Unterfangen ist leider nicht trivial, weil man nicht ohne Weiteres um die "Magie", die Wicket an vielen Stellen benutzt, herum kommt. Nun bietet Wicket die Möglichkeit, Komponenten und Seiten, Formulare und vieles mehr in Unit-Test zu testen. Was liegt also näher, als diese Funktionalität für unsere Zwecke zu missbrauchen.

Die E-Mail

Als erstes erstellen wir eine Seite, die den Inhalt der E-Mail erzeugen soll:

package de.wicketpraxis.web.blog.pages.questions.email;

import java.util.List;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;

public class EmailContentPage extends WebPage
{
  public EmailContentPage(IModel<List<? extends String>> list)
  {
    add(new ListView<String>("list",list)
    {
      @Override
      protected void populateItem(ListItem<String> item)
      {
        item.add(new Label("name",item.getModel()));
      }
    });
  }
}

Der Code soll zeigen, dass Komponenten ganz normal Funktionieren. Deshalb bekommt die Seite von außen eine Liste mit Werten übergeben, die dann durch eine ListView dargestellt wird.

<h1>TestEmail</h1>
<ul>
  <li wicket:id="list"><span wicket:id="name"></span></li>
</ul>

Im Markup habe ich absichtlich den "Rahmen" der Seite (html > body) weggelassen. Die Klasse und das Markup dienen in diesem Beispiel nur als Platzhalter.

Darstellen und Abschicken

In Wicket sind sehr viele Objekte an den aktuellen Thread gebunden. Das ist notwendig, damit man nicht eine ganze Liste von Objekten als Übergabeparameter durch Methodenaufrufe schleifen muss. Das ist allerdings genau dann ein Problem, wenn wir innerhalb einer Wicketanwendung eine andere Seite darstellen möchten. Außerdem ist es ein Problem, wenn es wie in einem im Hintergrund laufenden Prozess überhaupt keine WicketApplication gibt. Daher erstellen wir uns ein paar Hilfsklassen, die sich um dieses Problem kümmern.

package de.wicketpraxis.web.blog.pages.questions.email;

public interface WicketCallback<I,O>
{
  public O getResult(I input);
}

package de.wicketpraxis.web.blog.pages.questions.email;

import java.util.logging.Logger;

public class WicketThreadAdapter<I,O> extends Thread
{
  private static final Logger _logger = Logger.getLogger(WicketThreadAdapter.class.getName());
  
  Object _lock=new Object();
  
  WicketCallback<I, O> _callback;
  I _input;
  O _output;
  boolean _done=false;
  
  protected WicketThreadAdapter(WicketCallback<I, O> callback, I input)
  {
    _callback=callback;
    _input=input;
  }
  
  @Override
  public void run()
  {
    synchronized (_lock)
    {
      _output=_callback.getResult(_input);
      _done=true;
      _lock.notify();
    }
  }
  
  protected O getResult() throws InterruptedException
  {
    synchronized (_lock)
    {
      while (!_done)
      {
        _lock.wait();
      }
      return _output;
    }
  }
  
  public static <I,O> O getResult(WicketCallback<I, O> callback, I input) throws InterruptedException
  {
    WicketThreadAdapter<I,O> threadAdapter = new WicketThreadAdapter<I,O>(callback,input);
    threadAdapter.start();
    return threadAdapter.getResult();
  }
}

Die WicketThreadAdapter-Klasse wird über die statische Methode getResult(...) angesprochen und macht folgendes: Es wird ein neuer Thread erzeugt. Im laufenden Thread wird der Callback aufgerufen und der Thread beendet. Das Ergebnis, das im Thread ermittelt wurde, wird als Ergebnis herausgereicht. Kurz: Das Ergebnis wird in einem anderen Thread als dem aktuellen ermittelt und führt so nicht zu Kollisionen mit der laufenden Anwendung.

Der Trick

Das alles führt noch nicht zum Ziel. Wir müssen uns jetzt noch darum kümmern, dass wir die Seite dargestellt bekommen. Dazu benutzten wir die Klasse BaseWicketTester, die sich um alles weitere kümmert. Dort übergeben wir die Seite, die wir darstellen wollen. Dann können wir auf den Inhalt der Seite zugreifen und als Ergebnis zurück liefern.

package de.wicketpraxis.web.blog.pages.questions.email;

import java.util.Arrays;
import java.util.List;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.tester.BaseWicketTester;

public class EmailFromComponentPage extends WebPage
{
  public EmailFromComponentPage()
  {
    WicketCallback<List<String>, String> callback = new WicketCallback<List<String>, String>()
    {
      public String getResult(List<String> input)
      {
        final IModel<List<? extends String>> listModel = Model.ofList(input);
        
        BaseWicketTester tester=new BaseWicketTester();
        tester.startPage(new EmailContentPage(listModel));
        return tester.getServletResponse().getDocument();
      }
    };
    
    String result;
    try
    {
      result = WicketThreadAdapter.getResult(callback, Arrays.asList("Klaus","Susi","Bert"));
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
      result=e.getLocalizedMessage();
    }
    
    add(new Label("email",result).setEscapeModelStrings(false));
  }
}

Damit das Ergebnis richtig angezeigt wird, teilen wir dem Label "email" mit, dass es den Text nicht umschreiben soll.

<html>
  <head>
    <title>Email Form Component</title>
  </head>
  <body>
    Email <span wicket:id="email"></span>
  </body>
</html>

Wenn man die Seite aufruft, erhält man dann folgende Darstellung: Ergebnis: Html-E-Mail aus Wicket

Wenn man die E-Mail durch Hintergrundprozesse versenden möchte, ist ein eigener Thread unnötig, weil keine WicketApplication-Klasse an den Thread gebunden ist. Dann sollte der Einsatz der BaseWicketTester-Klasse reichen. Es ist allerdings darauf zu achten, dass alles, was in der eigenen Application-Klasse definiert wurde, an dieser Stelle nicht ohne weiteres zur Verfügung steht (z.B. wird der OpenSessionInViewFilter für Spring nicht aufgerufen, die SpringBean-Annotationen sind ebenfalls funktionslos).

Fazit

Auch wenn sicher noch die eine oder andere Anpassung notwendig ist, damit man Komponenten sowohl für die Anwendung als auch für den E-Mail-Versand benutzten kann, lohnt sich der Aufwand, weil man a) auf die vielfältigen Möglichkeiten, die Wicket als "Template-Engine" bietet, zurückgreifen kann, b) man sich im besten Fall doppelten Code ersparen kann und c) man keine zweite Lösung einbinden muss. Und wieder hat mich Wicket ein wenig überrascht. Weil es dann doch so einfach ging:)

Tags:

Veröffentlicht in Technologie, Wicket, .

Wicket nicht nur für Einsteiger

Ich durfte an einem Projekt teilhaben, in dem für eine neu zu erstellende Anwendung Wicket eingesetzt wurde. Am Anfang wurde das Projekt entsprechend der Anforderungen aufgesetzt (Maven, Hibernate, Spring, Wicket) und der Rahmen der Anwendung geschaffen (Konfiguration der Datenbank, Unittests, etc.). Nach einer doch recht kurzen Einführung in Wicket wurde die Anwendung ohne fremde Hilfe (wenn man mal Google mal ausklammert) entwickelt und mit immer mehr Funktionen angereichert. Gestern habe ich mir den aktuellen Stand angesehen und musste feststellen, dass entgegen meinen Erwartungen eine recht vernünftige Anwendung entstanden ist.

Warum habe ich das so nicht erwartet?

In dem Projekt war bisher wenig bis kein Wissen über Java und die benutzten Frameworks vorhanden. In der kurzen Einführung konnten weder die Sprache, noch die verwendeten Technologien ausführlich genug erklärt werden. Das bedeutet normalerweise, dass die Lösungen, die bei solchen Unterfangen entstehen, verschiedenste Defizite aufweisen. Neben der Tatsache, dass die Anwendung funktioniert (sicher gibt es noch irgendwo ein paar Fehler, aber wo gibt es die nicht), ist die Anwendung auch noch in einer ausreichend guten Verfassung, so dass die Struktur verstanden werden konnte und die Anpassungen, die wir durchgeführt haben, nicht dazu führten, dass etwas nicht mehr funktionierte.

Gründe

Ein nicht unwesentlichen Anteil daran, dass das Projekt besser als erwartet funktioniert hat, trägt sicherlich Wicket als Framework. Wenn man die Hürde genommen hat, die das Thema Modelle darstellt, ist der Rest recht offensichtlich. Eigene Komponenten werden erstellt, weil es a) sehr einfach ist und b) (viel entscheidender) man als Entwickler automatisch davon profitiert. Die Übersichtlichkeit der Anwendung entsteht auf diese Weise automatisch.

Fazit

Das eine Anwendung funktioniert, ist mindestens ein Maßstab zur Bewertung. Diese Anforderung ist gerade bei Webanwendungen nicht einfach zu erreichen. In Anbetracht der eher schlechten Voraussetzungen muss man das Ergebnis als gelungen bezeichnen. Da ich gebeten wurde, meine Einschätzung zu diesem Projekt abzugeben, freut es nicht nur mich, sondern sicher auch das Team, dass ich positiv überrascht bin.

Mit welcher anderen Technologie wäre dieses Projekt ähnlich gut verlaufen?

Tags:

Veröffentlicht in Allgemein, Refactoring, Wicket, .