Skip to content


Wicket Ajax – IAjaxRegionMarkupIdProvider

Fast unbemerkt versammeln sich in den Wicket-Updates neben einer Reihe von Bugfixes kleine Anpassungen, die das Entwickeln mit Wicket einfacher machen können. Da man nicht bei jedem Update nachsieht, was sich verändert hat, weil die Erfahrung gezeigt hat, dass man bei Wicket eigentlich immer auf die aktuelle Version wechseln kann, läuft man Gefahr, dass man wichtige Anpassungen verpasst. Deshalb stelle ich heute eine Erweiterung von Wicket vor, die in meinen Augen ein erhebliches Potential bietet: das Interface IAjaxRegionMarkupIdProvider.

Über dieses Interface ist es möglich, Wicket mitzuteilen, welches Seitenelement in einem Ajax-Response eigentlich ersetzt werden soll. So ist es möglich, z.B. in einem Behavior vor und nach der Komponente Html-Markup einzufügen, dass bei Aktualisierung der Komponente ebenfalls ersetzt wird. Was sich kompliziert anhört ist in der Anwendung recht einfach. Wir implementieren ein Interface, dass einen roten Rahmen um eine Komponente ziehen soll, die per Ajax aktualisiert wird.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.ajax.behavior;
02
 
03
import org.apache.wicket.Component;
04
import org.apache.wicket.ajax.IAjaxRegionMarkupIdProvider;
05
import org.apache.wicket.behavior.AbstractBehavior;
06
 
07
 
08
public class AjaxRegionBehavior extends AbstractBehavior implements IAjaxRegionMarkupIdProvider {
09
 
10
  @Override
11
  public void beforeRender(Component component) {
12
    component.getResponse().write("<div style=\"border:1px solid red;\" id=\""+getAjaxRegionMarkupId(component)+"\">");
13
    super.beforeRender(component);
14
  }
15
 
16
  @Override
17
  public void onRendered(Component component) {
18
    super.onRendered(component);
19
    component.getResponse().write("</div>");
20
  }
21
 
22
  @Override
23
  public String getAjaxRegionMarkupId(Component component) {
24
    return component.getMarkupId()+"_border";
25
  }
26
 
27
}

Das Behavior funktioniert wie folgt: In beforeRender() und in onRendered() wird um die Komponente herum ein div-Tag mit passender Style-Anweisung geschrieben. Hierbei muss in beforeRender() die id des Tags auf den Wert gesetzt werden, den Wicket dann auch über die Interface-Methode getAjaxRegionMarkupId() bekommt. Das führt dazu, dass Wicket die Ersetzung per Ajax automatisch auf den durch das Behavior definierten Rahmen, eben die ID, die per getAjaxRegionMarkupId() definiert wurde, ausweitet.

Die Anwendung ist recht einfach:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.ajax.behavior;
02
 
03
import org.apache.wicket.ajax.AjaxRequestTarget;
04
import org.apache.wicket.ajax.markup.html.AjaxLink;
05
import org.apache.wicket.markup.html.WebPage;
06
import org.apache.wicket.markup.html.basic.Label;
07
import org.apache.wicket.model.Model;
08
 
09
 
10
public class AjaxRegionPage extends WebPage {
11
 
12
 
13
  private Model<Integer> _counter;
14
 
15
  public AjaxRegionPage() {
16
 
17
    _counter = Model.of(0);
18
    final Label label=new Label("label",_counter);
19
    label.setOutputMarkupId(true);
20
    label.add(new AjaxRegionBehavior());
21
    add(label);
22
 
23
    add(new AjaxLink<Integer>("link",_counter) {
24
      @Override
25
      public void onClick(AjaxRequestTarget target) {
26
        setModelObject(1+getModelObject());
27
        target.addComponent(label);
28
      }
29
    });
30
  }
31
}

Wie man im Code-Beispiel sehen kann, wird das Behavior zur Komponente hinzugefügt. Würde man das Interface nicht implementieren, würden bei jeder Aktualisierung immer neue rote Rahmen um die Komponente entstehen.

 HTML |  copy code |? 
1
<html>
2
  <head>
3
    <title>AjaxRegion Page</title>
4
  </head>
5
  <body>
6
    <span wicket:id="label"></span>
7
    <a wicket:id="link">Count</a>
8
  </body>
9
</html>

Bisher war es nötig, diese Anforderung immer auf Komponenten abzubilden, die dann korrekt ersetzt werden mussten. Damit ist es jetzt sehr viel einfacher möglich, Eine Komponente zu dekorieren und diese Dekoration auch per Ajax korrekt zu ersetzten. Auch wenn der erste Gedanke einer Anwendung sich vermutlich um Formularfelder mit fehlerhaften Eingaben dreht, gibt es unzählige Möglichkeiten, wo diese Funktionalität die Entwicklung nachhaltig vereinfachen wird.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Allgemein.


Wicket – Komponentenübersicht (1.4.17)

Seit Wicket 1.4.1 haben sich bei den Komponenten und Interfaces ein paar Anpassungen ergeben, so dass ich die Komponentenübersichten aktualisiert habe. Folgende Diagramme beziehen sich auf die Version 1.4.17.

Wicket Kompontenten - Basis

Wicket Kompontenten – Basis

Wicket Komponenten inklusive Extensions

Wicket Komponenten inklusive Extensions

Wicket Modelle

Wicket Modelle

Wicket Behavior

Wicket Behavior

Wicket Listener

Wicket Listener

Wicket RequestTarget

Wicket RequestTarget

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Technologie, Wicket.


Wicket – Back Button zuverlässig erkennen

Die Entwicklung moderner Webanwendungen orientiert sich zunehmend am Interaktionsmodel wie es Desktopanwendungen bieten. Wicket unterstütz dabei erheblich, in dem z.B. der Einsatz von Ajax keine große Hürde mehr darstellt. Eine Interaktionsmöglichkeit ist Desktopanwendungen doch bisher eher fremd: der Back Button im Browser. Der Nutzer erwartet, dass er auf einen vorherigen Zustand zurück navigieren kann. Je komplexer die Webanwendung, desto aufwendiger war es mit anderen Frameworks, die Nutzererwartung auch in ein sinnvolles Anwendungsverhalten zu übertragen. Wicket bietet mit versionierten Seiten bereits eine gute Unterstützung, die einem Entwickler ermöglicht, die kleine “Zeitreise”, die der Nutzer vornimmt, auf Anwendungsseite entsprechend abbilden zu können.

Trotzdem möchte man unter Umständen wissen, dass der Nutzer den Back Button betätigt hat, weil es vielleicht auch einen Rückschluss darauf zulässt, dass der Nutzer in der Anwendung nicht die richtige Interaktionsmöglichkeit gefunden hat (z.B. in einem Bestellvorgang doch nochmal die Anschrift ändern zu wollen).

Folgendes Beispiel ist eine Möglichkeit, zu erkennen, ob der Nutzer mit dem Browser vor und zurück navigiert hat. Dabei gibt es zwei Dinge zu beachten. Der erste Seitenaufruf darf nicht in einer Url enden, die den Konstruktor der Seite aufruft. Denn dann sieht das aus Anwendungssicht so aus, als ob der Nutzer erneut auf die Seite navigiert ist. Zum zweiten muss man den Browser dazu zwingen, die Seite in jedem Fall neu vom Server abzurufen.

Daher brauchen wir zu erst eine Sprungseite:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.backbutton;
02
 
03
import org.apache.wicket.markup.html.WebPage;
04
 
05
public class BackButtonStartPage extends WebPage {
06
 
07
  public BackButtonStartPage() {
08
    setResponsePage(new BackButtonPage());
09
    setRedirect(true);
10
  }
11
}

Die Sprungseite ruft die eigentliche Seite auf. Außerdem ist der Aufruf von setRedirect() notwendig, damit nicht nur die neue Seite dargestellt wird, sondern sich auch die Url entsprechend ändert.

Die Seite mit der Erkennung der Button-Nutzung sieht dann wie folgt aus:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.backbutton;
02
 
03
import java.util.Date;
04
 
05
import org.apache.wicket.MetaDataKey;
06
import org.apache.wicket.Session;
07
import org.apache.wicket.markup.html.WebPage;
08
import org.apache.wicket.markup.html.link.Link;
09
import org.apache.wicket.markup.html.panel.FeedbackPanel;
10
import org.apache.wicket.protocol.http.WebResponse;
11
 
12
public class BackButtonPage extends WebPage {
13
 
14
  static final TimeStampKey KEY = new TimeStampKey();
15
  private long _pageInitTimeStamp;
16
 
17
  public BackButtonPage() {
18
    //setVersioned(true);
19
 
20
    _pageInitTimeStamp = new Date().getTime();
21
    setPageTimeStamp(_pageInitTimeStamp);
22
 
23
    add(new FeedbackPanel("feedback"));
24
 
25
    add(new Link<Void>("link") {
26
 
27
      @Override
28
      public void onClick() {
29
        setResponsePage(new BackButtonPage());
30
      }
31
    });
32
  }
33
 
34
  private static void setPageTimeStamp(long timeStamp) {
35
    Session.get().setMetaData(KEY, timeStamp);
36
  }
37
 
38
  @Override
39
  protected void onBeforeRender() {
40
    super.onBeforeRender();
41
 
42
    Long lastPageRendered = Session.get().getMetaData(KEY);
43
    setPageTimeStamp(_pageInitTimeStamp);
44
 
45
    if (lastPageRendered > _pageInitTimeStamp) {
46
      info("BackButton pressed");
47
    }
48
    if (lastPageRendered < _pageInitTimeStamp) {
49
      info("ForwardButton pressed");
50
    }
51
  }
52
 
53
  @Override
54
  protected void configureResponse() {
55
    super.configureResponse();
56
    WebResponse response = getWebRequestCycle().getWebResponse();
57
    response.setHeader("Cache-Control", "no-cache, max-age=0,must-revalidate, no-store");
58
  }
59
 
60
  static class TimeStampKey extends MetaDataKey<Long> {
61
 
62
  }
63
}

Das Markup der Seite:

 HTML |  copy code |? 
1
<html>
2
  <head>
3
    <title>BackButton Page</title>
4
  </head>
5
  <body>
6
    <div wicket:id="feedback"></div>
7
    <a wicket:id="link">Link</a>
8
  </body>
9
</html>

Das Prinzip funktioniert wie folgt: Die Seite bekommt einen Zeitstempel über den Zeitpunkt der Erstellung. Dieser Zeitpunkt wird außerdem als Metainformation in die Session gesetzt (Hinweis: um diese Funktion bei mehr als einer Seite benutzen zu können, ist natürlich etwas mehr Aufwand nötig). Das bedeutet, dass jede Version der Seite einen eigenen Zeitstempel hat. In onBeforeRender() kann man nun prüfen, ob diese Seite auch die aktuelle Seite ist, oder ob der Nutzer auf einen neuere oder ältere Version zurückgegriffen hat. Der Link dient dazu, immer neue Version der Seite zu erzeugen.

Damit der Browser die Seite aber in jedem Fall neu lädt, muss man entsprechende Header in den Http-Response einfügen. Eine Möglichkeit besteht darin, die configureResponse()-Methode entsprechend zu überschreiben.

Wenn man nun auf der Seite den Link klickt und dann mit dem Back Button oder dem Forward Button navigiert, erscheinen die entsprechenden Hinweise auf der Seite.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Allgemein, Wicket.

Tagged with , , , , , .


Wicket – Mainstream, Github, MongoDB

In den letzten Wochen und Monaten habe ich recht wenig neues über Wicket gelesen. Man könnte meinen, dass es ruhig geworden ist um Wicket. Doch wenn man sich z.B. die Mailinglisten ansieht, spricht das eine deutlich andere Sprache. Jobangebote fragen zunehmend ab, ob Wicket-KnowHow vorhanden ist. Man sieht mehr und mehr Anwendungen, die mit Wicket realisiert wurden. Wicket ist im Mainstream angekommen.

Vielleicht ist das auch nur eine sehr persönliche Sichtweise, aber ich glaube, das auch Git im Mainstream angekommen ist. Git wurde bereits in bestimmten Bereichen auf einer breiten Basis eingesetzt. Aber der bedeutende Unterschied liegt darin, dass man sich bei neuen Projekten eigentlich immer die Frage stellt: Subversion oder besser gleich Git?

Ich habe alle meine Projekte von Subversion auf Git umgestellt und dabei gemerkt, das Subversion und Git nicht wirklich aufeinander abbildbar sind, aber die Vorteile die Git mitbringt sich selbst dann auszahlen, wenn man Git noch wie Subversion benutzt. Man findet ab jetzt die Quelltexte für das Buch bei Github unter http://github.com/michaelmosmann/wicket-praxis.git.

Da ich in meinen aktuellen Projekten mich mit der Frage beschäftigt habe, ob nicht eine Dokumentendatenbank wie MongoDB die bessere Alternative ist, sei an dieser Stelle auch noch auf einen Objektmapper verwiesen, denn in ich diesem Zusammenhang entwickelt habe: http://github.com/michaelmosmann/mongoom.flapdoodle.de

In Zukunft an dieser Stelle wieder mehr und dann vielleicht rund um Wicket in Zusammenarbeit mit Dokumentendatenbanken.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Allgemein, Wicket.

Tagged with , , .


Wicket – Flexibilität mit Factories

Komplexe Komponenten entstehen in Wicket durch das zusammenfügen von einfacheren Komponenten. Dabei werden die verwendeten Komponenten direkt adressiert. Nach außen ist nicht sichtbar, wie sich eine Komponente zusammensetzt. Um von dieser Komponente eine leicht abgewandelte Form zu erstellen, kann man auf z.B. Vererbung zurückgreifen, Komponenten ausblenden, das Markup überschreiben. Je mehr Variationen nötig sind, desto komplizierter wird der Aufbau. Der Aufwand steigt erheblich an.

Einen Ausweg aus dieser Situation könnte die Verwendung von Factories liefern. Dazu benötigen wir ein sehr einfach gehaltenes Interface:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
1
package de.wicketpraxis.web.blog.pages.questions.factories;
2
 
3
import org.apache.wicket.Component;
4
 
5
public interface IComponentFactory<T extends Component>
6
{
7
  T newComponent(String id);
8
}

Eine einfach Implementierung, die immer ein Label mit einem bestimmten Text liefert, können wir wie folgt implementieren:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.factories;
02
 
03
import org.apache.wicket.markup.html.basic.Label;
04
import org.apache.wicket.model.IModel;
05
 
06
public class LabelFactory implements IComponentFactory<Label>
07
{
08
  IModel<?> _model;
09
 
10
  public LabelFactory(IModel<?> model)
11
  {
12
    _model = model;
13
  }
14
 
15
  public Label newComponent(String id)
16
  {
17
    return new Label(id,_model);
18
  }
19
}

Wir übergeben hierbei ein Model, das durch das Label angezeigt wird. Soll ein anderer Text angezeigt werden, muss man dafür eine neue Factory erstellen. Bis jetzt ist noch kein Vorteil dieser Lösung absehbar. Deshalb steigern wir etwas die Komplexität. Wir erstellen eine Factory, die einen Rahmen um eine Komponente ziehen kann. Dabei wird für die Darstellung das style-Attribut erweitert.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.factories;
02
 
03
import org.apache.wicket.AttributeModifier;
04
import org.apache.wicket.Component;
05
import org.apache.wicket.behavior.AttributeAppender;
06
import org.apache.wicket.markup.html.WebMarkupContainer;
07
import org.apache.wicket.markup.html.panel.Panel;
08
import org.apache.wicket.model.IModel;
09
 
10
public class BorderPanelFactory implements IComponentFactory<Panel>
11
{
12
  private final IComponentFactory<? extends Component> _content;
13
  private final IModel<String> _style;
14
 
15
  public BorderPanelFactory(IComponentFactory<? extends Component> content, IModel<String> style)
16
  {
17
    _content = content;
18
    _style = style;
19
  }
20
 
21
  public Panel newComponent(String id)
22
  {
23
    return new BorderPanel(id, _content, _style);
24
  }
25
 
26
  static class BorderPanel extends Panel
27
  {
28
    public BorderPanel(String id,IComponentFactory<? extends Component> content,IModel<String> style)
29
    {
30
      super(id);
31
 
32
      WebMarkupContainer border=new WebMarkupContainer("border");
33
      border.add(content.newComponent("content"));
34
      border.add(new AttributeAppender("style", true, style,";"));
35
      add(border);
36
    }
37
  }
38
}

Wir übergeben daher eine Factory, die Komponenten erzeugt und ein Model, dass die Styleattribute beinhaltet. In dem Panel, was innerhalb der Factory erzeugt wird, wir dann eine Komponente eingebunden (“content”), die aus der übergebenen Factory kommt. Wir benötigen noch eine passende Markup-Datei (BorderPanelFactory$BorderPanel.html):
 HTML |  copy code |? 
1
<wicket:panel>
2
  <div wicket:id="border" style="padding: 8px">
3
    <wicket:container wicket:id="content"></wicket:container>
4
  </div>
5
</wicket:panel>

Um zu demonstrieren, welche Flexibilität man mit diesen wenigen Klassen bereits erreicht hat, verwenden wir beide Factories in einem Beispiel:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.factories;
02
 
03
import org.apache.wicket.markup.html.WebPage;
04
import org.apache.wicket.model.Model;
05
 
06
public class ComponentFactoryPage extends WebPage
07
{
08
  public ComponentFactoryPage()
09
  {
10
    Model<String> redBorderStyle = Model.of("border:1px solid red; background-color: #fff0f0;");
11
    Model<String> greenBorderStyle = Model.of("border:1px solid green; background-color: #f0fff0;");
12
    Model<String> blueBorderStyle = Model.of("border:1px solid blue; background-color: #f0f0ff;");
13
 
14
    LabelFactory haveFunLabelFactory = new LabelFactory(Model.of("Have Fun"));
15
 
16
    BorderPanelFactory redBorderHasFunFactory = new BorderPanelFactory(haveFunLabelFactory,redBorderStyle);
17
    BorderPanelFactory greenBorderWrapsRedFactory = new BorderPanelFactory(redBorderHasFunFactory,greenBorderStyle);
18
    BorderPanelFactory blueBorderWrapsAllFactory = new BorderPanelFactory(greenBorderWrapsRedFactory,blueBorderStyle);
19
 
20
    add(blueBorderWrapsAllFactory.newComponent("element"));
21
  }
22
}

Wir erstellen 3 Modelle mit unterschiedlichen Werten für das style-Attribut. Um etwas Text anzuzeigen benutzen wir die LabelFactory. Danach werden drei BorderPanelFactory-Instanzen erzeugt, die eine andere Factory “umwickelt”. Zum Schluss wird ein Element erzeugt und in der Seite benutzt. Das Markup ist entsprechend einfach:

 HTML |  copy code |? 
1
<html>
2
  <head>
3
    <title>ComponentFactory Page</title>
4
  </head>
5
  <body>
6
    <wicket:container wicket:id="element"></wicket:container>
7
  </body>
8
</html>

Das Ergebnis sieht dann wie folgt aus:

Um zu zeigen, wie schnell die Möglichkeiten wachsen, die man mit diesem Ansatz abdecken kann, erstellen wir eine weitere Factory. In diesem Fall möchten wir zwei Elemente nebeneinander dargestellen:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.factories;
02
 
03
import org.apache.wicket.Component;
04
import org.apache.wicket.markup.html.panel.Panel;
05
 
06
public class TwoInARowFactory implements IComponentFactory<Component>
07
{
08
  private final IComponentFactory<? extends Component> _left;
09
  private final IComponentFactory<? extends Component> _right;
10
 
11
  public TwoInARowFactory(IComponentFactory<? extends Component> left, IComponentFactory<? extends Component> right)
12
  {
13
    _left = left;
14
    _right = right;
15
  }
16
 
17
  public Component newComponent(String id)
18
  {
19
    return new ContainerPanel(id, _left, _right);
20
  }
21
 
22
  static class ContainerPanel extends Panel
23
  {
24
    public ContainerPanel(String id,IComponentFactory<? extends Component> left, IComponentFactory<? extends Component> right)
25
    {
26
      super(id);
27
 
28
      add(left.newComponent("left"));
29
      add(right.newComponent("right"));
30
    }
31
  }
32
}

Es werden daher zwei Factories übergeben, die dann für die Erzeugung des linken und des rechten Elements zuständig sind. Das Markup benutzt der Einfachheit halber Html-Tabellen für die Anordnung:
 HTML |  copy code |? 
1
<wicket:panel>
2
  <table>
3
    <tr>
4
      <td><wicket:container wicket:id="left"></wicket:container></td>
5
      <td><wicket:container wicket:id="right"></wicket:container></td>
6
    </tr>
7
  </table>
8
</wicket:panel>

Unsere Seitenklasse ergänzen wir entsprechend:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.factories;
02
 
03
import org.apache.wicket.markup.html.WebPage;
04
import org.apache.wicket.model.Model;
05
 
06
public class ComponentFactoryPage extends WebPage
07
{
08
  public ComponentFactoryPage()
09
  {
10
    Model<String> redBorderStyle = Model.of("border:1px solid red; background-color: #fff0f0;");
11
    Model<String> greenBorderStyle = Model.of("border:1px solid green; background-color: #f0fff0;");
12
    Model<String> blueBorderStyle = Model.of("border:1px solid blue; background-color: #f0f0ff;");
13
 
14
    LabelFactory haveFunLabelFactory = new LabelFactory(Model.of("Have Fun"));
15
 
16
    BorderPanelFactory redBorderHasFunFactory = new BorderPanelFactory(haveFunLabelFactory,redBorderStyle);
17
    BorderPanelFactory greenBorderWrapsRedFactory = new BorderPanelFactory(redBorderHasFunFactory,greenBorderStyle);
18
    BorderPanelFactory blueBorderWrapsAllFactory = new BorderPanelFactory(greenBorderWrapsRedFactory,blueBorderStyle);
19
 
20
    add(blueBorderWrapsAllFactory.newComponent("element"));
21
 
22
    TwoInARowFactory twoInARowFactory = new TwoInARowFactory(redBorderHasFunFactory, greenBorderWrapsRedFactory);
23
 
24
    add(twoInARowFactory.newComponent("two"));
25
  }
26
}

Das Markup muss ebenfalls angepasst werden:

 HTML |  copy code |? 
1
<html>
2
  <head>
3
    <title>ComponentFactory Page</title>
4
  </head>
5
  <body>
6
    <wicket:container wicket:id="element"></wicket:container>
7
    <wicket:container wicket:id="two"></wicket:container>
8
  </body>
9
</html>

Das Ergebnis kann sich sehen lassen:

Wie man an diesem Beispiel sehr gut erkennen kann, liegt in diesem Ansatz sehr viel Potential, gerade wenn die Anforderungen an die Flexibilität sehr hoch sind. In Projekten, die eine hohe Flexibilität erforderten hat sich dieses System bereits erfolgreich bewährt. Dabei kommt eine Kombinationen aus dem “klassischen” und dem Factory-Ansatz zum Einsatz, wodurch sich die meisten Anforderungen wesentlich besser abdecken lassen.

Gibt es noch ganz andere Lösungsstrategien?

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Refactoring, Wicket.

Tagged with , , .