Skip to content


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, .

Migration zu Wicket : Formulare

Formulare sind wesentlicher Bestandteil einer Webanwendung. Doch gerade beim Umstieg von einem anderen Framework kommt es in diesem Bereich zu Irritationen. Warum? Ein wesentlicher Unterschied besteht darin, dass es in Wicket nicht zwingend eine Ergebnisseite geben muss. Ich möchte das an einem Beispiel veranschaulichen.

Als erstes erstellen wir eine JavaBean in der das Formular die Daten ablegt:

package de.wicketpraxis.web.blog.pages.questions.migration.forms;

import java.io.Serializable;

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

Nichts besonderes. Die Bean hat das Attribut "Name". Erstellen wir eine Seite, die über ein Formular diese Bean mit Werten füllt:

package de.wicketpraxis.web.blog.pages.questions.migration.forms;

import java.io.Serializable;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;

public class MinimalFormPage extends WebPage
{
  public MinimalFormPage()
  {
    Form<FormBean> form = new Form<FormBean>("form",new CompoundPropertyModel<FormBean>(new FormBean()));
    
    form.add(new TextField<String>("Name"));
    
    add(form);
  }
}

Wie wir sehen, wird ein Formular eingebunden und in dieses Formular ein Textfeld für das Attribut benutzt. Damit ist dieses Beispiel funktionsfähig. Es macht nur nicht besonders viel. Was aber gut zu erkennen ist:

  • es wird keine Ergebnisseite benötigt
  • es muss keine Methode überschrieben werden, um die Eingabedaten in der Bean abzulegen

Das Markup ist ebenfalls recht einfach:

<html>
  <head>
    <title>MinimalFormPage</title>
  </head>
  <body>
    <form wicket:id="form">
      Name <input wicket:id="Name"><br>
      <input type="submit" value="Absenden">
    </form>
  </body>
</html>

Interessant: der Submit-Button hat in diesem Beispiel keine Entsprechung als Wicket-Komponente. Das Formular wird trotzdem abgeschickt.

Komplexes Beispiel

Kommen wir nun zu einem etwas komplexeren Beispiel, dass eine Ergebnisseite verwendet. Dabei an dieser Stelle nocheinmal der Hinweis: die Ergebnisseite wird verwendet, weil wir das in diesem Beispiel so wollten. Alles was auf der Ergebnisseite dargestellt werden kann, könnte man auch ganz einfach auf der Seite darstellen, auf der auch das Formular eingebunden ist.

Als erstes erstellen wir die Ergebnisseite:

package de.wicketpraxis.web.blog.pages.questions.migration.forms;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;

public class ResultPage extends WebPage
{
  public ResultPage(String name)
  {
    init(name);
  }
  
  public ResultPage(PageParameters pageParameters)
  {
    init(pageParameters.getString("Name"));
  }
  
  protected void init(String name)
  {
    add(new Label("name",name));
  }
}
<html>
  <head>
    <title>ResultPage</title>
  </head>
  <body>
    Name <span wicket:id="name"></span>
  </body>
</html>

Die Seite besitzt zwei Konstruktoren. Der erste erwartet einen String als Parameter, der zweite wertet die Seitenparameter aus (wenn die Seite z.B. mit ?Name=klaus aufgerufen wird). Der Wert wird durch das Label zur Anzeige gebracht.

Jetzt die Formularseite:

package de.wicketpraxis.web.blog.pages.questions.migration.forms;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
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 KomplexFormPage extends WebPage
{
  public KomplexFormPage()
  {
    add(new FeedbackPanel("feedback"));
    
    Form<FormBean> form = new Form<FormBean>("form",new CompoundPropertyModel<FormBean>(new FormBean()))
    {
      @Override
      protected void onSubmit()
      {
        // Variante 1
        setResponsePage(new ResultPage(getModelObject().getName()));
        
        // Variante 2
        Map<String,String> map=new HashMap<String, String>();
        map.put("Name", getModelObject().getName());
        setResponsePage(ResultPage.class, new PageParameters(map));
        
        // Variante ?
      }
    };
    
    form.add(new TextField<String>("Name").setRequired(true));
    
    add(form);
  }
}

Wir haben in diesem Beispiel ein FeedbackPanel hinzugefügt, da wir auch das Textfeld zum Pflichtfeld gemacht haben. Ohne FeedbackPanel kommt sonst der Hinweis, dass man etwas eingeben muss, nicht zur Anzeige. Ansonsten haben wir die onSubmit-Methode überschrieben, die die Ergebnisseite aufruft. Dabei können wir zum einen eine Instanz der Seite erstellen, in dem wir (vorrausgesetzt es gibt einen passenden Konstruktor) den/die gewünschten Parameter direkt übergeben. Das wir in diesem Beispiel nur ein String übergeben, soll nicht darüber hinwegtäuschen, dass jedes beliebige (also auch weit komplexere) Objekte übergeben werden können.

Die zweite Variante verpackt den Parameter in einen Seitenparameter und springt die Seite dann über eine passende Url an. In diesem Fall werden natürlich die übergebenen Parameter für den Nutzer sichtbar. Das entspricht auch eher dem klassischen Verständnis einer Formularbehandlung in anderen Frameworks. Der Unterschied besteht allerdings schon darin, dass an dieser Stelle die Formularbehandlung bereits abgeschlossen ist und wir nur noch einmal absichtlich auf eine andere Seite springen.

Daher ist die letzte Variante auch nur mit einem Fragezeichen versehen. Wenn nichts dafür spricht, dass man eine wie auch immer geartete Ergebnisseite benötigt, kann man diese auch weglassen. Man sollte vielmehr darauf zurückgreifen, dass man Komponenten ein und ausblendet, wenn man das Formular nach erfolgreichem Ausfüllen nicht mehr anzeigen möchte.

Zusammenfassung

Ich hoffe, ich konnte zeigen, dass man sich vom dem klassischen Konzept einer Formularseite trennen muss und die Formularverarbeitung in Wicket ihre Entsprechung vermutlich eher in Swing findet.

Tags:

Veröffentlicht in Migration, Wicket, .