Formulare
Such-, Anzeige- und Bearbeitungsformulare mit Buttons und Validierung.
Formulare in 10 Schritten einbauen
Die Tour zeigt, wie sich das Kontrollelement auch nachträglich in eine bestehende Anwendung integrieren lässt.
FormTags - Formulartypen
Mit den Common Controls werden dem Pagedesigner Formulartypen geliefert, die immer wieder bei der Erstellung von Benutzeroberflächen benötigt werden. Durch die Verwendung der Formulartypen wird ein einheitliches Design innerhalb der Anwendung sichergestellt. Das Standarddesign kann auch an die eigenen Bedürfnisse (Corporate Identity) angepasst werden. Folgende Formulartypen stehen derzeit zur Verfügung:
- Eingabeformular
- Anzeigeformular
- Formular für Fehler- und Erfolgsmeldungen
- Suchdialog
- Kopfzeile
Gegenstand
In dieser Übung erstellen wir ein Eingabeformular und implementieren zwei Callback-Methoden für den Back- und Save-Button.
Das Formular enthält zwei Pflichtfelder, die bei einer fehlerhaften Validierung zu einer entsprechenden Meldung führen sollen:
In dieser Übung werden folgende Punkte behandelt:
- Auswahl des Designs für die Benutzeroberfläche
- Erstellung der Actionklasse
- Bereitstellung der Formulardaten
- Festlegung des Formularaufbaus in der JSP-Seite
- Implementierung der Callback-Methoden
- Validierung und Fehlerpräsentation
1. Registrierung der Painterfactory
Zuerst erfolgt die Registrierung der Painterfactory. Sie legt fest, welches Design die Benutzeroberfläche erhält. Dies kann in der init()-Methode des Frontcontroler-Servlets applikationsweit geschehen.1 Wir wählen hier das Standarddesign, welches uns der DefaultPainter bereitstellt.2
import javax.servlet.ServletExceptionimport org.apache.struts.action.ActionServlet;import com.cc.framework.ui.painter.PainterFactoryimport com.cc.framework.ui.painter.def.DefPainterFactory;import com.cc.framework.ui.painter.html.HtmlPainterFactory;public class MyFrontController extends ActionServlet { public void init() throws ServletException { super.init(); // Register all Painter Factories // with the preferred GUI-Layout // In this case we only use the Default-Layout. PainterFactory.registerApplicationPainter ( getServletContext(), DefPainterFactory.instance()); PainterFactory.registerApplicationPainter ( getServletContext(), HtmlPainterFactory.instance()); }}
*1) Wenn der einzelne Benutzer zwischen verschiedenen Oberflächendesigns wählen können soll, dann werden zusätzliche PainterFactorys in der Benutzersession registriert. Dies erfolgt meist in der LoginAction mit PainterFactory.registerSessionPainter() im Session Scope.
*2) Weitere Designs (PainterFactories) sind im Lieferumfang der Professional Edition enthalten, oder können selbst entwickelt werden.
2. Ableitung der Action Klasse für den Struts-Adapter
In unserem Formular möchten wir die Detailinformationen zu einem Benutzer bearbeiten. Daher soll die Action-Klasse, welches das Befüllen unseres Formulares übernimmt, die Bezeichnung "UserEditAction" tragen. Die Actionklasse wird dabei von der Klasse FWAction abgeleitet, welche die Struts-Action Klasse kapselt und um Funktionalitäten des Präsentationsframeworks erweitert. Dabei wird anstelle der execute()-Methode die doExecute()-Methode aufgerufen. [FWAction ist von org.apache.struts.action.Action abgeleitet] Sie erhält beim Aufruf den ActionContext, über den der Zugriff auf weitere Objekte, wie das Request- und Response-Objekt gekapselt ist.
import java.io.IOException;import javax.servlet.ServletException;import com.cc.framework.adapter.struts.FWActionimport com.cc.framework.adapter.struts.ActionContextpublic class UserEditAction extends FWAction { /** * @see com.cc.framework.adapter.struts.FWAction#doExecute(ActionContext) */ public void doExecute(ActionContext ctx) throws IOException, ServletException { // Code follows in the next chapter }}
3. Bereitstellung der Formulardaten
Zum Befüllen unseres Formulares dient uns eine FormBean, das UserEditForm, welches direkt von org.apache.struts.action.ActionForm abgeleitet werden kann. Die FormBean wird in unserem Beispiel vereinfacht mit unserem UserObjekt initialisiert, das zuvor die Daten zu dem im Request übergebenen Schlüssel aus einer Datenbank geladen hat.
import java.io.IOException;import javax.servlet.ServletException;import com.cc.framework.adapter.struts.ActionContext;import com.cc.framework.adapter.struts.FWAction;public class UserEditAction extends FWAction { /** * @see com.cc.framework.adapter.struts.FWAction#doExecute(ActionContext) */ public void doExecute(ActionContext ctx) throws IOException, ServletException { String userId = ctx.request().getParameter("userid"); try { // Load the User User user = new User(userId); user.load(); // Initialise the Form with the User-Data UserEditForm form = (UserEditForm) ctx.form(); form.setUser(user); // In our Example we store the UserObject // in our Session ctx.session().setAttribute("userobj", user); } catch (Throwable t) { ctx.addGlobalError("Error: ", t); ctx.forwardByName(Forwards.BACK); } // Call the JSP-Page with the Form ctx.forwardToInput(); }}
4. Festlegung des Formularaufbaus in der JSP-Seite
Um die Formular-Tags auf einer JSP Seite einzusetzen, muss am Anfang der Seite die entsprechende Tag Library deklariert werden. Anschließend können die Formularelemente mit dem Präfix <forms:tagname /> verwendet werden. [Zudem muss die Aufnahme der Tag Bibliothek im Deployment-Deskriptor, der WEB-INF/web.xml Datei, erfolgen]. Unser Formular enthält neben Eingabefeldern und Auswahlboxen auch einen Abschnitt zur Gruppierung von Informationen und eine Button-Leiste. Die benötigten Elemente werden über die folgenden Tags definiert:
- <forms:form/> definiert das Formular und legt die Überschrift fest.
- <forms:plaintext/> dient zur Ausgabe von Text.
- <forms:text/> erzeugt ein Eingabefeld. Zur Kennzeichnung von Pflichtfeldern dient das required-Attribut.
- <forms:select/> definiert eine Auswahlbox.
- <forms:options/> definiert eine Optionsliste für eine Auswahlbox.
- <forms:section/> definiert und zeichnet einen Abschnitt.
- <forms:buttonsection/> definiert eine Button-Leiste und legt einen Default-Button fest.
- <forms:button/> definiert und zeichnet einen Button.
- <forms:message/> zeichnet einen Meldungsdialog. Über das Attribut severity="error" wird der Dialog als Fehlerdialog klassifiziert.
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %><%@ taglib uri="/WEB-INF/tlds/cc-forms.tld" prefix="forms" %><%@ taglib uri="/WEB-INF/tlds/cc-controls.tld" prefix="ctrl" %><forms:message caption="Error" severity="error"/><html:form action="/sample101/userEdit"> <forms:form type="edit" caption="User - Edit" formid="frmEdit"> <forms:plaintext label="User-Id" property="userId"/> <forms:text label="First-Name" property="lastName" size="45" required="true"/> <forms:text label="Last-Name" property="firstName" size="45" required="true"/> <forms:select label="Role" property="rolekey"> <ctrl:options property="roleOptions"/> </forms:select> <forms:text label="eMail" property="email" size="45"/> <forms:text label="Phone" property="phone" size="25" /> <forms:section title="Address"> <forms:text label="Street" property="street" size="45"/> <forms:text label="Number" property="streetnumber" size="5"/> <forms:text label="ZipCode" property="zipcode" size="5"/> <forms:text label="City" property="city" size="25"/> <forms:select label="Country" property="countrycode"> <ctrl:options property="countryOptions" labelProperty="country"/> </forms:select> </forms:section> <forms:buttonsection default="btnSave"> <forms:button name="btnBack" src="btnBack1.gif"/> <forms:button name="btnSave" src="btnSave1.gif"/> </forms:buttonsection> </forms:form></html:form>
Das <forms:form>-Tag ist in unserem Beispiel in ein Struts-Tag <html:form/> eingebettet. Es erhält damit über die angegebene Action (/sample101/userEdit) den Zugriff auf die Form-Bean, welche die Anzeigedaten bereitstellt. Das <forms:form>-Tag kann auch allein ohne das Struts-Tag verwendet werden, dann muss jedoch zusätzlich das action-Attribut angegeben werden. Alle Tags der Common-Controls-Bibliothek arbeiten bei Bedarf im Verbund mit den Struts-Tags.
5. Implementierung der Callback-Methoden
Der Back- und der Save-Button in unserem Formular erzeugen jeweils ein Click-Event, auf das wir innerhalb unserer Action durch die Aufnahme zweier Callback-Methoden reagieren können. Der Name der Methode setzt sich dabei aus dem Namen der Schaltfläche und dem Suffix onClick zusammen. Formularbuttons müssen dabei mit dem Präfix "btn" benannt werden, ansonsten wird keine Callback-Methode aufgerufen. Der Button btnBack führt damit zum Aufruf der Methode back_onClick. Der Methode wird der FormActionContext übergeben, welcher den Zugriff auf das Request- und das Session-Objekt sowie die FormBean kapselt.
import java.io.IOException;import javax.servlet.ServletException;import com.cc.framework.adapter.struts.ActionContext;import com.cc.framework.adapter.struts.FWAction;import com.cc.framework.adapter.struts.FormActionContext;public class UserEditAction extends FWAction { /** * @see com.cc.framework.adapter.struts.FWAction#doExecute(ActionContext) */ public void doExecute(ActionContext ctx) throws IOException, ServletException { // Code see above } // ------------------------------------------------ // Event Handler // ------------------------------------------------ /** * This Method is called when the Back-Button is pressed. * @param ctx FormActionContext */ public void back_onClick(FormActionContext ctx) { ctx.forwardByName(Forwards.BACK); } /** * This Method is called when the Save-Button is pressed. * @param ctx FormActionContext */ public void save_onClick(FormActionContext ctx) { // See next Chapter }}
6. Validierung und Fehlerpräsentation
Die Validierung der Daten erfolgt in unserem Beispiel mittels der validate()-Methode in der FormBean. Die Methode wird innerhalb der UserEditAction aufgerufen, sobald der Save-Button auf unserem Formular angeklickt wurde. Das Formular kann vor dem Feld, in dem ein Fehler aufgetreten ist, einen entsprechenden visuellen Hinweis erzeugen. Hierzu wird die Fehlermeldung unter Angabe des entsprechenden Properties in die ActionErrors-Collection eingestellt. Die nachfolgende Validierung führt zu der Meldung "Input required for Field: First Name" und zeigt ein Warnzeichen vor dem entsprechenden Feld an, wenn dort keine Eingabe erfolgt ist.
import javax.servlet.http.HttpServletRequest;import org.apache.struts.action.ActionError;import org.apache.struts.action.ActionErrors;import org.apache.struts.action.ActionMapping;public class UserEditForm extends UserDisplayForm { /** * @see org.apache.struts.action.ActionForm#validate() */ public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors(); if ("".equals(firstName) ) { errors.add("firstName", new ActionError( "Input Required for Field: ", "First Name")); } if ("".equals(lastName) ) { errors.add("lastName", new ActionError( "Input Required for Field: ", "Last Name")); } return errors; }}
Die Validierung wird in der save_onClick()-Methode angestoßen. Auftretende Fehler werden dabei in den FormActionContext eingestellt. Dies führt dazu, dass nach einer Rückkehr auf die Eingabeseite die entsprechenden Fehlermeldungen angezeigt werden. Wenn die Validierung erfolgreich war, können die Änderungen in die Datenbank übernommen werden. Bei diesem Vorgang können ebenfalls Fehler auftreten. Solche Fehler werden in unserem Beispiel nicht in der Eingabemaske angezeigt, sondern in der Maske, von der aus wir in den Bearbeitungsmodus verzweigt sind. Die Fehlermeldung wird dazu ebenfalls in den Kontext eingestellt und dann die entsprechende Action aufgerufen. Die Trialversion der Common Controls enthält dazu den ausführlichen Quelltext. Im Erfolgsfall wird eine entsprechende Meldung an den Anwender weitergegeben; der Text wird über die Methode addGlobalMessage() in den FormActionContext eingestellt. Anschließend wird die Eingabemaske wieder verlassen.
import java.io.IOException;import javax.servlet.ServletException;import com.cc.framework.adapter.struts.ActionContext;import com.cc.framework.adapter.struts.FWAction;import com.cc.framework.adapter.struts.FormActionContext;public class UserEditAction extends FWAction { // other code see above ... public void save_onClick(FormActionContext ctx) { UserEditForm form = (UserEditForm) ctx.form(); // Validate the Formdata ActionErrors errors = form.validate(ctx.mapping(), ctx.request()); ctx.addErrors(errors); // If there are any Errors return and display a Message if (ctx.hasErrors()) { ctx.forwardToInput(); return; } try { // In our Example we get the User-Object // from the Session User user = (User) ctx.session().getAttribute("userobj"); populateBusinessObject(ctx, user); user.update(); } catch (Throwable t) { ctx.addGlobalError("Error: ", t); ctx.forwardByName(Forwards.BACK); return; } // Generate a Success Message ctx.addGlobalMessage( "Data updated: ", form.getUserName()); ctx.forwardByName(Forwards.SUCCESS); }}
Tour Ende
Das Beispiel hat gezeigt, wie sich durch den Einsatz von Form-Tags Oberflächen schneller erstellen lassen. Dabei wird die Einhaltung eines einheitlichen Designs innerhalb der Anwendung gewährleistet. Es lassen sich aber auch andere Designs durch eine Anpassung der Painter realisieren, und verschiedene Designs können parallel verwendet werden.
Features der Form-Tags:
- Bereitstellung von zahlreichen Formularelementen (Text, Plaintext, Textarea, Select, Button, Buttonsection, File, Password, Radio, Spin, Description, Checkbox, Section)
- Gruppierungsmöglichkeit von Formularelementen
- Formularelemente können einfach mit einem Label, Beschreibungstext, Erforderliche Eingabe etc. in der JSP-Seite deklariert werden.
- Abbildung von Formularereignissen auf Event-Handler in der Action-Klasse.
- Visualisierung bei Fehleingaben.
- Unterstützung von Hover-Effekten bei Buttons
- Design des Formulares in der JSP-Seite oder auch serverseitig definierbar.
- Design durch Painterfactory an eigenen StyleGuide (Corporate Identity) anpassbar.
- Gleiches Look and Feel in Microsoft InternetExplorer > 5.x und Netscape Navigator > 7.x
Exkurs: Bereitstellung von Buttons mit Hover-Effekt
Zur Darstellung des Hover-Effektes, wird ein Button im aktiven Zustand und ein Button im selektierten Zustand benötigt. Der aktive Button wird dabei als gif-Datei mit dem Präfix btn und dem Suffix 1.gif abgelegt (z.B. btnBack1.gif). Für den Hover-Effekt wird ein Button mit der Endung 3.gif benötigt (z.B. btnBack3.gif).
Namenskonventionen und Zustände für Formularbuttons:
- Aktiv: btnXXX1.gif
- Inaktiv: btnXXX3.gif
- Aktiv selektiert: btnXXX5.gif
- Gedrückt: btnXXX6.gif
Die Bilder werden automatisch ausgetauscht, sobald der Mauszeiger über einen Formularbutton bewegt wird. Zuständig hierfür ist ein JavaScript-Eventhandler, der für das MouseOver- und das MouseOut-Event in der Datei fw/def/jscript/controls.js registriert wird.
Dieses Script wird von dem Default Painter automatisch in jede HTML Seite inkludiert.
Fertige Konfigurationen zum Übernehmen
Screenshot, Konfiguration und der zugehörige JSP-Code.
Konfiguration
- Zwei Textfelder für Suchkriterien.
- Image für austauschbaren Button (Hover-Effekt). Das Bild für den inaktiven Zustand wird unter dem Namen btnXXX1.gif abgelegt. Das Bild für den Hover-Effekt unter dem Namen btnXXX3.gif. Die Bilder werden automatisch beim überqueren des Buttons mit der Maus ausgetauscht.
<forms:form type="search" caption="Manufacturer Search" formid="frmSearch"> <forms:text label="Id" property="id" size="11" maxlength="10"/> <forms:text label="Name" property="name" size="25" maxlength="80"/> <forms:buttonsection> <forms:button name="btnSearch" src="fw/scc/image/buttons/btnSearch1.gif"/> </forms:buttonsection></forms:form>
Konfiguration
- Formatierung der Überschrift und Formularbreite
- Aufnahme verschiedener Ausgabefelder für die anzuzeigenden Informationen.
- Definition eines Unterabschnittes
- Aufnahme eines Back-Buttons, der in der Action zum Aufruf einer back_onClick()-Methode führt.
<forms:form type="display" caption="User - Display" formid="frmDisplay" width="450"> <forms:plaintext label="User-Id" property="userId"/> <forms:plaintext label="Name" property="userName"/> <forms:plaintext label="Role" property="role.value"/> <forms:plaintext label="eMail" property="email"/> <forms:plaintext label="Phone" property="phone"/> <forms:section title="Address"> <forms:plaintext label="Street" property="fullStreet"/> <forms:plaintext label="City" property="fullCity"/> <forms:plaintext label="Country" property="fullCountry"/> </forms:section> <forms:buttonsection> <forms:button styleId="btnBack" name="btnBack" src="fw/cc/image/buttons/btnBack1.gif" title="Back"/> </forms:buttonsection></forms:form>
Konfiguration
- Formatierung der Überschrift und Formularbreite
- Aufnahme der Eingabe- und Auswahlfelder. Festlegen der Pflichteingabefelder.
- Definition eines Unterabschnittes.
- Aufnahme eines Back-Buttons, der in der Action zum Aufruf einer back_onClick()-Methode führt.
- Aufnahme eines Save-Buttons, der in der Action zum Aufruf einer save_onClick()-Methode führt.
<forms:form type="edit" caption="User - Edit" formid="frmEdit"> <forms:plaintext label="User-Id" property="userId"/> <forms:text label="First-Name" property="lastName" size="45" required="true"/> <forms:text label="Last-Name" property="firstName" size="45" required="true"/> <forms:select label="Role" property="rolekey"> <ctrl:options property="roleOptions"/> </forms:select> <forms:text label="eMail" property="email" size="45" maxlength="256"/> <forms:text label="Phone" property="phone" size="25" /> <forms:section title="Address"> <forms:text label="Street" property="street" size="45" maxlength="80"/> <forms:text label="Number" property="streetnumber" size="5"/> <forms:text label="ZipCode" property="zipcode" size="5"/> <forms:text label="City" property="city" size="25"/> <forms:select label="Country" property="countrycode"> <ctrl:options property="countryOptions" labelProperty="country"/> </forms:select> </forms:section> <forms:buttonsection default="btnSave"> <forms:button name="btnBack" src="fw/cc/image/buttons/btnBack1.gif" title="Back"/> <forms:button name="btnSave" src="fw/cc/image/buttons/btnSave1.gif" title="Save"/> </forms:buttonsection></forms:form>
Konfiguration
Für dieses Beispiel sind keine Stichpunkte hinterlegt – die Konfiguration ergibt sich aus dem Code.
<forms:form type="edit" caption="User - Edit" formid="frmEdit"> <forms:plaintext label="User-Id" property="userId"/> <forms:text label="First-Name" property="lastName" size="45" required="true"/> <forms:text label="Last-Name" property="firstName" size="45" required="true"/> <forms:select label="Role" property="rolekey"> <ctrl:options property="roleOptions"/> </forms:select> <forms:text label="eMail" property="email" size="45" maxlength="256"/> <forms:text label="Phone" property="phone" size="25" /> <forms:section title="Address"> <forms:text label="Street" property="street" size="45" maxlength="80"/> <forms:text label="Number" property="streetnumber" size="5"/> <forms:text label="ZipCode" property="zipcode" size="5"/> <forms:text label="City" property="city" size="25"/> <forms:select label="Country" property="countrycode"> <ctrl:options property="countryOptions" labelProperty="country"/> </forms:select> </forms:section> <forms:buttonsection default="btnSave"> <forms:button name="btnBack" src="fw/cc/image/buttons/btnBack1.gif" title="Back"/> <forms:button name="btnSave" src="fw/cc/image/buttons/btnSave1.gif" title="Save"/> </forms:buttonsection></forms:form>