Practical Wicket

Wicket hat sich als ernstzunehmendes Framework für die Entwicklung von komplexen Informationssystemen etabliert. UI-Komponenten lassen sich einfach und intuitiv entwickeln, der Support durch die Community aus Anwendern und Entwicklern ist ausgezeichnet.

Diese Merkmale sind aber häufig nicht die ausschlaggebenden Argumente für oder wider den Einsatz eines Frameworks. Meist steht die Integration in eine vorgegeben Infrastruktur aus Frameworks, Entwicklungsrichtlinien, Wissensstand der Mitarbeiter an erster Stelle bei den Auswahlkriterien.

Welch gute Figur dabei Wicket macht zeigen wir anhand diverser Beispiele aus unserer täglichen Praxis. Von der Zusammenarbeit mit diversen Peristenzframeworks, Wickets Integration mit Spring oder CDI, der Integration von JQuery bis hin zur Arbeitserleichterung mit IDE-Plugins - anhand von konkreten Beispielen zeigen wir wie einfach die Arbeit mit Wicket sein kann.

Web-Apps und Java

We are...

Bert Radke +BertRadke

Senior Developer, Architekt bei T-Systems MMS

XML is like violence. If it doesn't solve your problem, you're not using enough of it.

Marco Grunert @magomi

Entwickler, Architekt, Operations, Trainer bei intecsoft GmbH & Co. KG

An optimist person will say that the glass is half-full.

A pessimist person will say that the glass is half-empty.

A programmer will say that the glass is twice as large as necessary.

Agenda

  • Warum Apache Wicket?
  • Grundaufbau einer Wicket-Anwendung
  • Komponenten mit Daten verknüpfen
  • Sichere Wicket Anwendungen
  • Wicket für Macher
  • Und nun...?

Once upon a time...

  • 2005: Wicket 1.0
  • ...
  • 2010: Wicket 1.5
    • Generics
  • 2012: Wicket 6
    • Events
    • Integration von JQuery
    • Überarbeites Resourcehandling
  • 2014: Wicket 7 beta

Why Wicket?

  • steile Lernkurve: Just Java and HTML
  • Separation of Concerns
  • Secure
  • Reusable Components
  • Support of Backbutton
  • Well-designed API
  • http://wicket.apache.org/meet/features.html

Los gehts...


mvn archetype:generate -DarchetypeGroupId=org.apache.wicket \
                       -DarchetypeArtifactId=wicket-archetype-quickstart \
                       -DarchetypeVersion=6.14.0 \
                       -DgroupId=de.practicalwicket -DartifactId=demo \
                       -DarchetypeRepository=https://repository.apache.org/ \
                       -DinteractiveMode=false                       
          

Projektstruktur

Java & HTML


public class HomePage extends WebPage {
    private Person goethe = new Person("Goethe", "Mehr Licht!");
    public HomePage() {
        setDefaultModel(new CompoundPropertyModel<Person>(goethe));
        add(new Label("name"));
        add(new Label("famousLastWord"));
    }
    private class Person {
        private String name;
        private String famousLastWord;
        private Person(String name, String famousLastWord) {...}
    }
}
          

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
    <head<title>Famous last Words</title></head>
    <body<
        <span wicket:id="name">[name]</span> sagte 
        <span wicket:id="famousLastWord">[noch nichts]</span>
    </body>
</html>
          

einheitliches Seitenlayout

Vererbung/Comp.

  • Geerbt von Basisklasse

  • Von spez. Klasse bereitgestellt

  • Komponenten

Vererbung/Comp.

  • Geerbt von Basisklasse

  • Von spez. Klasse bereitgestellt

  • Komponenten

Vererbung Base Page


public class BasePage extends WebPage {
    private final String title;
    public BasePage(String title) {
        this.title = title;
    }
    @Override
    protected void onInitialize() {
        super.onInitialize();
        add(new Label("title", title));
        add(new BookmarkablePageLink<HomePage>("homepage", HomePage.class));
        add(new BookmarkablePageLink<Page1>("page1", Page1.class));
        add(new BookmarkablePageLink<Page2>("page2", Page2.class));
    }
}
          

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
   <head>
      [...]
   </head>
   <body>
      [...]
      <wicket:child>
      </wicket:child>
      [...]
   </body>
</html>
          

Vererbung Sub Page


public class Page1 extends BasePage {
    public Page1() {
        super("Page 1");
    }
}
          

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <body>
      <wicket:extend>
      <h1>Page 1</h1>
   </wicket:extend>
</body>
</html

Composition


public class BasePage extends WebPage {
   [...]
   @Override
   protected void onInitialize() {
      [...]
      add(getFooterPanel("footerPanel"));
   }
   public component getFooterPanel(String wicketID) {
      return new EmptyPanel(wicketID);
   }
}
     

public class Page1 extends BasePage {
   [...]
   @Override
   protected Component getFooterPanel(String wicketID) {
      return new Page1FooterPanel(wicketID);
   }
}
          

Formulare

  • Formulare durch IModels an Daten knüpfen
  • einfache Hierarchie
    • Formular
    • Eingabekomponenten
    • Aktionen
  • automatische Konvertierungen
  • Validatoren

Formular und Felder


   Form<Registration> form = new Form<Registration>(
           "form",
           new CompoundPropertyModel<Registration>(new Registration())) {
       @Override
       protected void onSubmit() {
          super.onSubmit();
          setResponsePage(new RegistrationSuccess(getModel()));
       }
   };
form.add(new TextField<String>("email"));
form.add(new TextField<String>("userName"));
form.add(new PasswordTextField("password"));
				

Convertierung


public interface IConverter<C> extends IClusterable {

   C convertToObject(String value, Locale locale) throws ConversionException;

   String convertToString(C value, Locale locale);
}
                

Validierung

  • Verschiedenste Validatoren mitgeliefert, z.B:
    • Zahlen, Textlängen
    • Pattern, Range
    • E-Mail
  • eigene Validatoren sind einfach:

userName.add(new IValidator<String>() {
    @Override
    public void validate(IValidatable<String> validatable) {
        if (validatable.getValue().length() < 4 ) {
            ValidationError validationError
                    = new ValidationError().addKey("username.error");
            validationError.setVariable("userName", validatable.getValue());
            validatable.error(validationError);
         }
    }
});
				

Fehlerbehandlung

  • FeedbackPanel
  • INFO, SUCCESS, WARNING, ERROR

Markierung fehlerhafter Componenten


   form.visitFormComponents(new IVisitor<FormComponent<?>, Object>() {
      @Override
      public void component(FormComponent<?> component, IVisit<Object> visit) {
           component.add(new ErrorMarkingBehavior());
      }
   });
                

public class ErrorMarkingBehavior extends Behavior {
    @Override
    public void onComponentTag(Component component, ComponentTag tag) {

        if ( ! ((FormComponent)component).isValid()) {
            String oldValue = tag.getAttribute("class");
            if (oldValue == null) {
                tag.put("class", "error");
            } else {
                tag.put("class", "error " + oldValue);
            }
        }
    }
}

Eigene Form als wiederverwendbare Komponente

  • Markierung fehlerhafter Felder
  • Markierung von Pflichtfeldern
  • Plazierung der Errormessage nahe am Formelement

Models

  • Bindeglied zwischen Wicket Components und Daten

public interface IModel<T> extends IDetachable {

	T getObject();

	void setObject(final T object);
}
                

public interface IDetachable extends IClusterable {
	void detach();
}
                

Modelimplementierungen

  • Model Model.of(), Model.ofList(), Model.ofSet()
  • PropertyModel, CompoundPropertyModel
  • LoadableDetachableModel
  • AbstractReadonlyModel
  • ChainingModel
  • ...

Page Parameters

gibt es Webframeworks ohne?


public class WicketApplication extends WebApplication {
    @Override
    public void init() {
        mountPage("mountingDemo/named/${author}/#{foo}", MountingIndexed.class);
        mount(new MountedMapper("mountingDemo/query"
                    , MountingQuery.class, new UrlPathPageParametersEncoder()));
    }
}
                

public class MountingIndexed extends BasePage {
    public MountingIndexed(PageParameters parameters) {
        super("mounting page with named parameters");
        String author = parameters.get("author").toString();
        String foo = parameters.get("foo").toString();
    }
}
                

public class MountingQuery extends BasePage {
    public MountingQuery(PageParameters parameters) {
        super("mounting page with query parameters");
        String author = parameters.get("demoParam").toString("default");
        String foo = parameters.get(1).toString("default-foo");
        add(new Label("par0", author));
        add(new Label("par1", foo));
    }
}
                

Securing the App

Listen to Wicket

  • IAuthorizationStrategy
  • IUnauthorizedComponentInstantiationListener
  • Annotation on Components

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SecuredPage {
}
                

Resource handling

Static


<link href='http://fonts.googleapis ... ' rel='stylesheet' type='text/css'>
<link href="css/bootstrap.css" rel="stylesheet" >
<link href="css/style.css" rel="stylesheet" >
<script src="js/bootstrap.js"></script>
                
  • And Components?

Dynamic


@Override
public void renderHead(HtmlHeaderContainer container) {
    super.renderHead(container);

    container.getHeaderResponse()
        .render(CssHeaderItem.forReference(new BootstrapCssReference()));
    container.getHeaderResponse()
        .render(CssHeaderItem.forReference(new ApplicationCssReference()));
    container.getHeaderResponse()
        .render((CssHeaderItem.forUrl("http://fonts.googleapis ... ")));

    container.getHeaderResponse()
        .render(JavaScriptHeaderItem.forReference(new BootstrapCssReference()));

Resourcen on the fly erzeugen

  1. ResourceStream implementieren
  2. Dynamische (AJAX) Komponente die an Buttons gebunden werden kann
  3. Einbindung in UI-Komponente

Internationalisierung

  • Resourcebundles (xxx_de.properties, xxx_en.properties)
  • sprachspezifische HTML-Files
  • sprachspezifische Bilder
  • Style in Session definierbar

Test

  • Build-In Unittesting
  • Klasse WicketTester als zentrale Komponente
  • Test von
    • Interaktionen
    • Status von Componenten
    • Responses
    • Ajax-Calls
  • Überprüfung des erzeugten Markup

WicketTester

  • WicketTester als Einstieg
 				
WicketTester tester = new WicketTester(new WicketApplication());
                
  • diverse assert-Methoden auf Wickettester
 				
tester.startPage(HomePage.class);
tester.assertRenderedPage(HomePage.class);
tester.assertLabel("title", "Home Page");
tester.clickLink("registration");
tester.assertLabel("title", "Registration");
                

FormTester

  • FormTester zum Testen von Formularen
  • Eingabe von Daten
  • Formular submitten
  • Prüfung von Fehlermeldungen
 				
WicketTester tester = new WicketTester(new WicketApplication());
tester.startPage(RegistrationPage.class);
FormTester formTester = tester.newFormTester("form", false);
formTester.setValue("userName", "jb");
[...]
formTester.submit();
tester.assertErrorMessages("invalid user name: jb");
                

Dependency Injection

  • Spring
  • JEE CDI
  • Guice

Spring

  1. Listener in web.xml definieren
  2. Pfad zur Kontextdefinition eintragen
  3. Laden des Kontext in Applikation initieren
  4. Viel Spass mit Spring!

IDEs

  • Ist das wirklich nötig?
  • Einbindung über Plugins
    • Eclipse: Qwickie
    • IDEA: WicketForge
    • Netbeans: ???
  • WicketSource
    • Kopplung von Browser und IDE
    • Öffnen des Quellcodes direkt aus Browser
    • IDEs: Eclipse und IDEA
    • Browser: Firefox und Chrome

Alles nur Theorie?

Energieabrechnung 1

Energieabrechnung 2

Sitzungsverwaltung

Mautsystem 1

Mautsystem 2

Vorgangsverwaltung A

Vorgangsverwaltung B

...und Andere?

Ich will mehr!

Online Resources

Bücher

Communities

Danke für den Fisch die Aufmerksamkeit!

Branches auf https://github.com/magomi/practicalwicket

  • master: Quickstart
  • step_0_5: Verknüpfung HTML <-> Java
  • step_0_6: Spring Integration
  • step_1: Page Vererbung
  • step_1_5: OnTheFly-Generierung
  • stept_2: Page Composition
  • step_3: Form Processing (infach)
  • step_4: Form mit FeedbackPanel, IValidator, @required,...
  • step_5: Form mit Markierung von fehlerhaften Elementen
  • step_5_5: Unit-Tests
  • step_6: abgeleitete Form mit automatischer Markierung von Pflichtfeldern und Errors
  • step_7: PageParameters
  • step_8: Resource Management
  • step_9: Absicherung von Seiten