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: Ajax

Einer der Aspekte, die bei meiner Entscheidung für ein neues Webframework eine große Rolle spielten, war die Integration von Ajax. Fast jedes aktuelle Framework bietet eine Unterstützung von Ajax, doch unterscheiden sie sich in Handhabung und Möglichkeit.

Die komponentenorientierte Arbeitsweise von Wicket führt dazu, dass eine Wicket-Seite (die Entsprechung einer Webseite), aus einem Komponentenbaum besteht, bei der jede Komponente für ihre Darstellung verantwortlich ist. Daher ist es besonders einfach, den Teil zu identifizieren, der durch die jeweilige Komponente dargestellt wird. Diesen Umstand macht sich Wicket zu nutze, um im Gegensatz zu anderen Frameworks, beliebig viele Bereiche der Webseite aktualisieren zu können.

Im folgenden Beispiel soll das veranschaulicht werden:

package de.wicketpraxis.web.blog.pages.migration.ajax;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.Model;

public class AjaxMultiUpdatePage extends WebPage
{
  public AjaxMultiUpdatePage()
  {
    final FeedbackPanel feedback=new FeedbackPanel("feedback");
    feedback.setOutputMarkupId(true);
    add(feedback);
    
    // die geschweiften Klammern haben keine Funktion
    // sie sollen nur den Komponentenbaum veranschaulichen
    WebMarkupContainer block = getBlock(feedback,1);
    {
      WebMarkupContainer block2 = getBlock(feedback,2);
      {
        WebMarkupContainer block3 = getBlock(feedback,3);
        block2.add(block3);
      }
      block.add(block2);
    }
    add(block);
  }

  private WebMarkupContainer getBlock(final FeedbackPanel feedback, int start)
  {
    final WebMarkupContainer block = new WebMarkupContainer("ajaxUpdate");
    block.setOutputMarkupId(true);
    
    Model<Integer> model = Model.of(start);
    final Label label = new Label("count",model);
    label.setOutputMarkupId(true);
    block.add(label);
    
    block.add(new AjaxFallbackLink<Integer>("link",model)
    {
      @Override
      public void onClick(AjaxRequestTarget target)
      {
        setModelObject(getModelObject()+1);
        info("Aktualisiere "+getPath());
        if (target!=null)
        {
          target.addComponent(feedback);
          target.addComponent(block);
          
          // ist nicht nötig
          target.addComponent(label);
        }
      }
    });
    
    return block;
  }
}

Wir fügen der Seite ein FeedbackPanel hinzu. Für jede Komponente, die per Ajax aktualisiert werden soll, muss die setOutputMarkupId(true) aufgerufen werden. Dass veranlasst Wicket, in dem Html-Tag dieser Komponente das id-Attribut zu setzen. Das ist notwendig, damit Wicket diesen Tag dann durch neuen Inhalt ersetzen kann.

Als weiteres erzeugen wir einen WebMarkupContainer. Dieses Element dient uns als Rahmen, um die Aktualisierung des ganzen Blocks anstoßen zu können. Als Kindelemente definieren wir zusätzlich ein Label, das den Inhalt eines Modells darstellt, dass wir gleichzeitig in einem AjaxFallbackLink benutzen. Klickt der Nutzer auf den Link, wird der Wert im Modell erhöht und der Block sowie das FeedbackPanel in die Liste der zu aktualisierenden Komponenten aufgenommen. Auch das Label wurde zu dieser Liste hinzugefügt. Allerdings kommt hier bereits eine Optimierung zum Vorschein, die Wicket vornehmen kann, da der Komponentenbaum bekannt ist: Wicket ersetzt den ganzen Block und weiß, dass damit auch das Label aktualisiert wurde, weshalb es nicht gesondert ersetzt werden muss.

Innerhalb eines Blockes fügen wir einen weiteren als Kindelement ein. Das wiederholen wir noch einmal. Wird in dieser Konstellation nun der äußere Block aktualisiert, werden auch alle Kindelemente aktualisiert. Dieses Verhalten kann man überprüfen, in dem man die Kommunikation im WicketAjaxDebug-Fenster betrachtet.

Wicket Ajax Debug Window

Deaktiviert man JavaScript und damit Ajax, dann funktioniert die Seite trotzdem, da dann die ganze Seite neu gerendert wird. Auf diese Weise kann man Webanwendungen mit Ajax-Unterstützung entwickeln, ohne dass man Nutzer, die aus welchen Gründen auch immer JavaScript deaktiviert haben, im Regen stehen lässt.

<html>
  <head>
    <title>AjaxMultiUpdate</title>
    <style type="text/css">
      .block
      {
        border:1px solid dashed;
      }
    </style>
  </head>
  <body>
    <h1>Ajax Test</h1>
    <div wicket:id="feedback"></div>
    
    <div wicket:id="ajaxUpdate" class="block">
      <span wicket:id="count">123</span> <a wicket:id="link">+1</a>
      <div wicket:id="ajaxUpdate" class="block">
        <span wicket:id="count">123</span> <a wicket:id="link">+1</a>
        <div wicket:id="ajaxUpdate" class="block">
          <span wicket:id="count">123</span> <a wicket:id="link">+1</a>
          
        </div>
      </div>
    </div>
  </body>
</html>

Dieses Beispiel offenbart wesentliche Unterschiede zwischen der Ajax-Integration von Wicket und anderen Frameworks:

  • Wicket kann beliebig viele Komponenten innerhalb eines Request aktualisieren.
  • Wicket kann die Aktualisierung optimieren.
  • Auch bei deaktiviertem Javascript ist die Funktion der Anwendung gewährleistet.

Tags:

Veröffentlicht in Migration, Wicket, .

Grails Ajax Update zu kompliziert...

Man lernt ja jeden Tag etwas neues. Gestern habe ich gelernt, dass Grails von Haus aus nur ein Element per Ajax austauschen kann. Man klickt auf einen Link und kann dann z.B. den Inhalt eines DIV-Tags ersetzten, ergänzen oder was auch immer. Das ist alles ganz nett und einfach. Ändert sich aber durch eine Interaktion mehr als ein Element auf der Seite, fängt es an kompliziert zu werden. Wobei kompliziert eigentlich nicht das richtige Wort ist, denn man muss diese fehlende Funktionalität selbst ergänzen.

Es ist unzweifelhaft möglich, so das gewünschte Ziel zu erreichen. Aber ich finde es spannend, dass es eine IMHO derart triviale Anforderung nicht in den "Grails-Standard" geschafft hat. In Wicket füge ich die Komponenten, die aktualisiert werden müssen, einfach zu einer Liste hinzu. Ich muss mich nicht darum kümmern, was dann genau passiert.

Festzuhalten bleiben für mich die Punkte, die für mich eher gegen einen Einsatz von Grails sprechen:

  • Ajax Update nur für ein Element möglich.
  • Spring Security ist pfadbasiert - das ist IMHO ein extrem limitierter Ansatz.
  • Taglibs sind kein Ersatz für Komponenten.

Grails hat Stärken.. aber ich bezweifle zunehmend, dass in Projekten, die mit Grails angefangen wurden, Grails diese positiven Effekte aus dem Projektstart auch auf lange Sicht ausspielen kann.

Update:

Immerhin habe ich gerade wieder gelernt, dass man Spring Security auch mit Annotationen am Controller benutzen kann, so dass man die Pfade nicht von Hand aktualisieren muss, wenn man etwas umbaut.

Das ich hier vielleicht Wicket und Grails vergleiche, obwohl die beiden Technologien nicht so einfach vergleichbar sind, bezieht sich in dieser Kritik nur auf den View-Layer. Es gibt sehr viele Dinge, die ich recht cool finde, an Grails. Aber um View-Layer bin ich einfach besseres gewohnt.

Tags:

Veröffentlicht in Technologie, Wicket, .

onclick statt Link

Was mir eigentlich nur durch Zufall auffiel, ist die Tatsache, dass man in Wicket einen Link auch an andere Tags als "a","area", etc binden kann. Netterweise macht Wicket dann daraus einen Javascript-Aufruf. Das geht mit einem Link und (wesentlich interessanter) auch mit einem AjaxFallbackLink.

Tags:

Veröffentlicht in Wicket, .