Tapestry
Inhaltsverzeichnis
Start
Die Informationen dieser Seite basieren auf Tapestry 4.1.
Installation
Voraussetzungen
- Java Development Kit (JDK)
- Ohne JDK lässt sich nichts entwickeln, also bei Sun runterladen.
- Integrated Development Environment (IDE)
- Das ist die Entwicklungsumgebung. Die bekannteste ist vermutlich Eclipse.
- Bei Eclipse ist empfehlenswert, die WebToolsPlattform (WTP) mitzuinstallieren.
- Servlet container
- Optional: Maven
- Wenn Maven genutzt wird, einfach die dependencies ins
Pom.xml
schreiben:
- Wenn Maven genutzt wird, einfach die dependencies ins
... <dependencies> ... <dependency> <groupId>org.apache.tapestry</groupId> <artifactId>tapestry-framework</artifactId> <version>4.1.6</version> </dependency> <dependency> <groupId>org.apache.tapestry</groupId> <artifactId>tapestry-annotations</artifactId> <version>4.1.6</version> </dependency> ... </dependencies> ...
Bei Maven nicht vergessen, den Compiler auf version 1.5 oder 1.6 zu setzen:
... <build> ... <plugins> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> ... </plugins> ... </build> ...
Ohne Maven muss man Tapestry manuell runterladen und zu den Libraries von Eclipse hinzufügen.
Hier eine Anleitung dazu, falls notwendig. Ist auch gleich ein kleines Tutorial dabei.
Struktur
Eine Web-Applikation in Java hat immer einen Ordner WEB-INF
. Generell (bei Tapestry) gehören alle HTML-, .page-, .jwc- und .application-Files in diesem Ordner.
Damit Tapestry überhaupt geladen wird, muss es einem Servlet zugewiesen werden. Das tut man im web.xml
(auch im WEB-INF-Ordner):
... <servlet> <servlet-name>App</servlet-name> <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>App</servlet-name> <url-pattern>/app</url-pattern> </servlet-mapping> ...
Der Wert im Element <load-on-startup>1</load-on-startup>
definiert, in welcher Reihenfolge die Servlets gestartet werden.
Ein Wert >= 0 startet diesen Servlet automatisch, sobald der Servletcontainer läuft. Ein Wert < 0 oder leer startet diesen Servlet irgendwann, spätestens
beim ersten Aufruf.
Dieser Wert ist optional.
Dateien
Tapestry startet mit Home. Das heist, es braucht eine Home.html
, Home.page
und Home.java
.
Datei | Zweck | Notizen |
---|---|---|
Home.html | Html File für die visualisierung der Daten | x |
Home.page | Beschreibung, welche Java-Datei für diese Html-Datei zuständig ist | Wird in bindings konfiguriert (mehrere bindings sind moeglich) |
Home.java | Java-Datei mit Daten und Logik | Meistens abgeleitet von BasePage (extend BasePage) |
Inhalte
Version 1 (declared) | Version 2 (implicit) |
---|---|
<span jwcid="now">…</span> |
<span jwcid="@Insert" value="ognl:currentDate">…</span> |
|
<!-- no longer needed <component id="now" type="Insert"> <binding name="value" value="currentDate"/> </component> --> |
public getCurrentDate(){ return "jetzt"; } |
public getCurrentDate(){ return "jetzt"; } |
- Home.page alternatives values
- <binding name="value" value="ognl:currentDate"/>
- ==> gleich wie: <binding name="value" value="currentDate"/>
- <binding name="value" value="literal:my birthday"/>
- ==> gleich wie <binding name="value" value="’my birthday’"/>
- ==> ’my birthday’ wird als String ausgegeben (ohne ' ' natuerlich)
- ==> gleich wie <binding name="value" value="’my birthday’"/>
Abstrakte Methoden
anstelle von: | abststrakt: |
---|---|
public String getTheWord() { return theWord; }
|
public abstract String getTheWord(); public abstract void setTheWord(String theWord);
|
Nicht vergessen, dass wenn man sich für die abstrakte Methoden entscheidet, Tapestry auch das Initialisieren und somit auch das Löschen der Werte übernimmt, bevor ein anderer Benutzer drauf zugreift. Siehe auch Properties.
Komponenten
Eigene Komponenten
Eigene Komponenten können (sind) Webseiten, die in Pages eingefügt werden können. Sie lassen sich so überall in der Applikation wiederverwenden.
Um Komponenten zu erstellen, braucht es ein HTML- und ein jwc-File (im Gegensatz zur Page, die ein page-File braucht).
Komponenten können dann in Pages (oder Komponenten) so eingefügt werden:
<div jwcid="@MeineKomp" />
Ein jwc-File enthaelt im Minimum diesen Code:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE component-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd"> <component-specification />
Falls eine Java-Klasse zugewiesen ist, wird <component-specification />
wie folgt erweitert:
Beispiel: <component-specification class="ch.miggiano.app.comp.SerienList" /> </component-specification>
Die Klasse 'muss' von BaseComponent
abgeleitet sein (extends BaseComponent
).
Es ist empfehlenswert, die Ablage der Komponenten in einem eigenen Ordner zu organisieren. Damit das geht, muss man in die
app.application diese Zeile eintragen:
Beispiel: <meta key="org.apache.tapestry.component-class-packages" value="ch.imis.ishop.web.comp"/>
Das heisst, alle Komponenten sind im Ordner comp
abgelegt
Parameter
Wenn man der eigenen Komponente Parameter übergeben will, kann man das z.B. mit der annotation @Parameter
machen.
Beispiel:
Wenn man z.B. eine Komponente Suchergebnis erstellt, und die braucht die Parameter (Searchtext, FromPage, ToPage), kann man das so machen:
- Suchergebnis.java
@Parameter(name = "searchtext") public abstract String getSearchValue(); @Parameter(name = "fromPage") public abstract int getFromPage(); @Parameter(name = "toPage") public abstract int getToPage();
- Aufruf von Suchergebnis.html aus einer anderen html-Seite
<div jwcid="@Suchergebnis" searchtext="'Audi RS6'" fromPage="4" toPage="5" />
oder
<div jwcid="@Suchergebnis" searchtext="ognl:selectedItem" fromPage="ognl:thisPage + 1" toPage="ognl:thisPage + 2"/>
Die Angabe vom Parameter 'name' ist optional. Wenn man es nicht angibt, heisst der Parameter gleich wie die sofort folgende Methode.
Doku: Tapestry4 annotations parameter
Tapestry Komponenten
Komponentenname (type) | Beschreibung | Beispiel |
---|---|---|
Insert | Text einfuegen |
|
TextField | Textfeld (in einem Formular) |
... <input type="text" jwcid="firstName"/> <input type="text" jwcid="lastName"/> ... <component id="firstName" type="TextField"> <binding name="value" value="firstName"/> </component> <component id="lastName" type="TextField"> <binding name="value" value="lastName"/> </component> public abstract String getFirstName(); public abstract String getLastName();
|
Form | HTML-Form |
<form action="" jwcid="secretWordForm">
<component id="secretWordForm" type="Form"> <binding name="listener" value="listener:onWordSubmit"/> </component>
public String onWordSubmit() { return "Secret"; }
|
PageLink | Link zu einer bestimmten Tapestry-Page |
<a href="" jwcid="@PageLink" page="CelebritiesList"> List Celebrities </a>
|
For | Iterator über eine Collection oder Array (For each) | Bei diesem Beispiel funktionieren die Links nicht! Diese müssen mit @DirectLink implementiert werden.
<table width="300" cellpadding="5" border="1"> <tr> <th>Last Name</th> <th>First Name</th> </tr> <tr jwcid=”eachCelebrity”> <td> <a href=""> <span jwcid="lastName">Smith</span> </a> </td> <td> <span jwcid="firstName">John</span> </td> </tr> <tr jwcid=”$remove$”> <td> <a href="">Smithson</a> </td> <td>Jane</td> </tr> <tr jwcid=”$remove$”> <td> <a href="">Swedenborg</a> </td> <td>Emmanuel</td> </tr> </table> <page-specification class="com.devshed.tapestry.celebrities.CelebritiesList"> <property name="currentCelebrity"/> <component id="eachCelebrity" type="For"> <binding name="source" value="celebrities"/> <binding name="value" value="currentCelebrity"/> </component> <component id="firstName" type="Insert"> <binding name="value" value="currentCelebrity.firstName"/> </component> <component id="lastName" type="Insert"> <binding name="value" value="currentCelebrity.lastName"/> </component> </page-specification>
|
DirectLink | Im Gegensatz zu @PageLink hat @DirectLink folgende (Haupt-)Vorteile:
|
Basiert auf die Beispiele von <a href="" jwcid="detailsLink"> <span jwcid="lastName">Smith</span> </a> <component id="detailsLink" type="DirectLink"> <binding name="listener" value="listener:onShowDetails"/> <binding name="parameters" value="currentCelebrity.id"/> </component> <component id="birthday" type="Insert"> <binding name="value" value="celebrity.dateOfBirth"/> <binding name="format" value="dateFormat"/> </component> public abstract class CelebritiesList extends BasePage { private DataSource dataSource = new DataSource(); @InjectPage("Details") public abstract Details getDetailsPage(); public List getCelebrities() { return dataSource.getCelebrities(); } public IPage onShowDetails(int id) { Celebrity celebrity = dataSource.getCelebrityById(id); Details nextPage = getDetailsPage(); nextPage.setCelebrity(celebrity); return nextPage; } } public abstract class Details extends BasePage { public abstract Celebrity getCelebrity(); public abstract void setCelebrity(Celebrity c); public Format getDateFormat() { return new SimpleDateFormat("MMM d, yyyy"); } }
|
Radio und RadioGroup | xx |
|
PropertySelection | Creates form elements that allow a property of an object to be set from a drop-down list. |
<select jwcid="occupation"> <component id="occupation" type="PropertySelection"> <binding name="model" value="occupationsModel"/> <binding name="value" value="occupation"/> </component> public abstract String getOccupation(); public IPropertySelectionModel getOccupationsModel() { String[] occupations = {"Actor/Actress", "Wine-maker", "Programmer"}; return new StringPropertySelectionModel(occupations); }
|
x | xx | xxx |
x | xx | xxx |
Spezielle jwcid
$remove$
Die Komponente wird von Tapestry nicht gerendert. So kann eine statische Seite gebaut werden mit Beispieldaten und diese später von Tapestry ignorieren lassen.
Beispiel:
<table> <tr> <th>First Name</th> <th>Last Name</h> </tr> <tr jwcid="loop"> <td><span jwcid="insertFirstName">John</span></td> <td><span jwcid="insertLastName">Doe</span></td> </tr> <tr jwcid="$remove$"> <td>Frank</td> <td>Smith</td> </tr> <tr jwcid="$remove$"> <td>Jane</td> <td>Jones</td> </tr> </table>
Vorsicht!
Es sind keine Komponenten in einem $remove$-Block erlaubt. Dies verursacht eine Exception von Tapestry.
Tapestry '$remove$ jwcid'
$content$
TODO
Properties
Die typischen Getter und Setter inkl. deren private
Variablen nennen sich Properties.
Wo | Beispiel | Notizen |
---|---|---|
In der Java-Klasse | public abstract String getTheWord();
|
Abstrakte Methode benutzen. Man muss sich keine Sorgen machen um die Zurücksetzung der Daten vor dem Initialisieren.
|
In der Java-Klasse |
|
Ausprogrammieren. Mehr Möglichkeiten, aber man darf nicht vergessen, die Variablen zurückzusetzen beim Initialisieren. |
In der Java-Klasse |
|
Kombination der ersten Beiden. Ist vermutlich der beste Ansatz. |
Page-File | <property name="theWord"/>
|
Ist gleichwertig wie die Abstrakte Methode. Wird meistens dann eingesetzt, wenn ein temporäres Objekt gebraucht wird, z.B. bei einem |
x | xx | xxx |
Falls man ein Startwert definieren will, geht man so vor:
- Abstrakt-Methode
- Vor der Abstrakt-Methode wird eine Annotation gesetzt:
@InitialValue("literal:enter a word") public abstract String getTheWord();
Da InitialValue
eine OGNL-Expression ist, ist auch folgendes möglich:
@InitialValue("initialValue") public abstract String getTheWord(); public String getInitialValue() { return "enter a word"; }
- Page-File
- Property-Tag wird um
initial-value
erweitert:
<property name="theWord" initial-value="literal:enter a word"/>
- Manuell in der Java-Klasse
- Indem man
getTheWord()
überschreibt:
public String getTheWord() { if (theWord == null) theWord = “enter a word"; return theWord; }
- Manuell in der Java-Klasse
- Oder indem man
finishLoad()
überschreibt (ausBasePage
):
protected void finishLoad() { theWord = “enter a word"; }
Injection (mit Annotations)
Dieser Code-Fragment sorgt in dieser Klasse dafür, dass über getDetailsPage()
ein Verweis auf die Page Details
vorhanden ist:
@InjectPage("Details") public abstract Details getDetailsPage();
Spezielle Interfaces
PageBeginRenderListener
Bei Komponenten (oder Pages), die eine einmalige Aktion ausführen müssen, bevor die Seite geladen wird, kann man das InterfacePageBeginRenderListenerimplementieren.
Dies bedingt die Methode
public void pageBeginRender(PageEvent event)
Listener
Ist die Methode, die ausgeführt wird z.B. bei einem Form submit.
Simple Listener
Beispiel:
- Home.html
-
<form action="" jwcid="secretWordForm"> … some content … </form>
- Home.page
-
<component id="secretWordForm" type="Form"> <binding name="listener" value="listener:onWordSubmit"/> </component>
Wir wollen also die Methode onWordSubmit()
aufrufen:
public String onWordSubmit() { return "Secret"; }
Das zurückgeben eines Strings bedeutet, dass Tapestry die Seite mit dem zurückgegebenen Namen anzeigen soll.
In diesem Fall 'Secret.html'.
Listener mit Referenz auf andere Pages
public IPage onWordSubmit(IRequestCycle cycle) { Secret nextPage = (Secret)cycle.getPage("Secret"); nextPage.setTheWord(getTheWord()); return nextPage; }
- IPage
- Ist ein Interface von Tapestry. Alle pages implementieren dies. Also wird mit diesem Listener eine page zurückgegeben (die, wo wir hinwollen).
- IRequestCycle
- Bestandteil von majordomo. Jeder User hat ein majordomo. Er verwaltet unter Anderem die pages. In diesem Fall bekommen wir eine Referenz zu jeder beliebigen Tapestry-page.
-
cycle.getPage("Secret")
- Gibt eine
IPage
zurück. Da wir wissen, dass es die pageSecret.html (bzw. Secret.java)
ist, können wir sie mit(Secret)
casten.
Listener mit Referenz auf andere Pages und Parameters
Einfache Variante ohne IRequestCycle.getListenerParameters()
Mit einem Parameter
public IPage onWordSubmit(Integer id) { // some code here ... }
<a jwcid="@DirectLink" listener="listener:onWordSubmit" parameters="ognl:element.id" > </a>
Mit mehrereren Parameter
public IPage onWordSubmit(Integer id, String name, Integer page) { // some code here ... }
<a jwcid="@DirectLink" listener="listener:onWordSubmit" parameters="ognl:{ element.id, element.name, currentPage }" > </a>
Alternative mit IRequestCycle.getListenerParameters()
Mit einem Parameter
public IPage onWordSubmit(IRequestCycle cycle) { Object[] parameters = cycle.getListenerParameters(); String theWord = (String)parameters[0]; Secret nextPage = (Secret)cycle.getPage("Secret"); nextPage.setTheWord(theWord); return nextPage; }
<a jwcid="@DirectLink" listener="listener:onWordSubmit" parameters="ognl:theWord"> <span jwcid="@Insert" value="ognl:theWord" /> </a>
Mit mehrereren Parameter
public IPage onWordSubmit(IRequestCycle cycle) { Object[] parameters = cycle.getListenerParameters(); String theWord = (String)parameters[0]; String theSecondWord = (String)parameters[1]; Secret nextPage = (Secret)cycle.getPage("Secret"); nextPage.setTheWord(theWord); return nextPage; }
<a jwcid="@DirectLink" listener="listener:onWordSubmit" parameters="ognl:{ theWord, theSecondWord }"> <span jwcid="@Insert" value="ognl:theWord" /> </a>
OGNL Expressions
Wenn eine Methode keine Klammern hat, geht Tapestry davon aus, dass es Properties sind, stellt also get und set vorne ran:
- ognl:person.address.town
- getPerson().getAddress().getTown()
- ognl:currentDate + someMessage
- getCurrentDate() + getSomeMessage()
Wenn man Klammern setzt, geht Tapestry von einer Methode aus. Get und set werden nicht gesetzt:
- ognl:doThat()
- doThat()
- ognl:getList(currChar)
- getList(Char currChar)
Kombinationen:
- ognl:theWord == 'abrakadabra'
- getTheWord == 'abrakadabra' => getTheWord().equals("abrakadabra")
- ognl:'backoffice' + important
- 'backoffice' + getImportant() => "backoffice" + getImportant()
Conditions
<div jwcid="@If" condition="ognl:theWord == 'abrakadabra'"> <!—Stuff to show if everything is OK --/> </div> <div jwcid="@Else"> <!-- An alternative content goes here --> </div>
Diesmal wurde ein div
statt ein span
benutzt, weil div
auch andere Elemente beinhalten kann im Gegensatz zu span
, der nur zur Formatierung von Text benutzt wird.
Das ognl hier ist wichtig, denn sonst wird es als Text interpretiert und dann ist alles, was nicht leer ist, true.
Wichtig für das @Else
ist, dass es sofort nach dem @If
kommt.
Annotations
HiveMind
Tapestry 4 braucht HiveMind für IoC (Inversion of Control). Da HiveMind schon implementiert ist, braucht es nur konfiguriert zu werden, und zwar in hivemodule.xml.
hivemodule.xml
<?xml version="1.0" encoding="UTF-8"?> <module id="com.devshed.tapestry.celebrities" version="1.0.0"> <contribution configuration-id="tapestry.state.ApplicationObjects"> <state-object name="dataSource" scope="session"> <create-instance class="com.devshed.tapestry.celebrities.DataSource"/> </state-object> </contribution> </module>
Element | Beschreibung | Beispiel |
---|---|---|
<module> |
The <module> element is the root of hivemodule.xml; we shall have it in every file like this. Note that the version attribute should look exactly as shown, i.e. it must contain three numbers separated by periods, or the application won't start. |
xxx |
<contribution> |
The <contribution> element tells HiveMind that we are going to contribute something of our own to its already existing extensive list of services and objects. The tapestry.state.ApplicationObjects value for configuration ID explains what we are going to do with this object; it indicates an ASO. |
xxx |
<state-object> |
The <state-object> element gives the ASO its name under which we shall be requesting it in the pages. Also we have specified that the scope for this ASO is session. Session is basically a piece of memory associated specifically with the given user; nobody else can access this memory. So in our case there will be a separate instance of DataSource for each user, which makes sense to me, as different collectors will have different collections. Alternatively, we could use an application scope, and in that case all the users would share the same DataSource. |
xxx |
<create-instance> |
Finally, the <create-instance> element specifies which class to instantiate to create the desired ASO (Application State Object -> A DataSource stored in the application's memory and easily accessible for all pages. |
xxx |
x | xx | xxx |
x | xx | xxx |
Usage
Um ein Element aus hivemodule zu benutzen, folgenden Beispielcode benutzen:
@InjectState("dataSource") public abstract DataSource getDataSource();
Dieser Code kann überall in der Applikation benutzt werden. Mit getDataSource()
greift man somit auf
die Klasse com.devshed.tapestry.celebrities.DataSource
zu.
Localization
Quelle: Tapestry 4 localization
Wenn man eine Page oder Component für eine Spezielle Locale zur verfügung stellen will, z.B.
weil der Login in deutscher Sprache mehr Felder braucht als der auf französisch, kann man
diese z.B. mit Login_de.html von Login_fr.html voneinander unterscheiden.
Auch Länderspezifisch: Login_de_DE.html bzw. Login_de_CH.html
Config
app.application
Speicherort der app.application: WEB-INF
Benennung: app muss dem Applikationsnamen (Servlet-Name) entsprechen (wie im web.xml definiert). Gross und Kleinschreibung sind wichtig!
-> %servletname%.application
Beispiel:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE application PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd"> <application> <meta key="org.apache.tapestry.page-class-packages" value="ch.imis.ishop.web"/> <meta key="org.apache.tapestry.component-class-packages" value="ch.imis.ishop.web.comp"/> <meta key="org.apache.tapestry.messages-encoding" value="UTF-8"/> <meta key="org.apache.tapestry.accepted-locales" value="de_CH,fr_CH,it_CH,de_DE" /> </application>
Notizen
Abkuerzungen
- ognl
- Object-Graph Navigation Language: it is an expression language for getting and setting properties of Java objects.
- jwcid
- Java Web Component ID
- @
- Komponente
- listener
- Methode, die aufgerufen wird, wenn das Formular übermittelt wird.
Some notes
userdataForm@Form <== page oder komponente ^ |- entspricht der id im der Website (<form id="userdataForm" ...) Pages und Komponenten haben Java-Klassen mit gleichem Namen .html = Pages .html + .jwc = Komponenten .jwc = Informationen zu entspr. Java-Klasse Beispiel für ein Label (aus Properties-File) <th class="label"> <span jwcid="@insert" value="message:doc.number"> irgendein Text (wird von Tapestry ersetzt, ausser man ruft das File ohne Tapestry auf) </span> </th>
Vorlagen
- *.page
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://tapestry.apache.org/dtd/Tapestry_4_0.dtd"> <page-specification class="ch.miggiano.app.Home"> </page-specification>
- *.jwc
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE component-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd"> <component-specification class="ch.miggiano.app.comp.SerienList" /> </component-specification>