tapestry components for web 2.0
DESCRIPTION
Tapestry Components for Web 2.0. Howard Lewis Ship TWD Consulting, Inc. [email protected]. What is Tapestry?. Application. Page. Page. Component. Component. Component. Component. Component. Component. Controller. View. Model. Java Beans. HTML template. Dynamic HTML output. - PowerPoint PPT PresentationTRANSCRIPT
2
What is Tapestry?
Application
PagePage
ComponentComponent ComponentComponent
ComponentComponent
3
Controller
ModelView
HTML template Java Beans
Dynamic HTML output
Links / Form Submissions
4
Login Form
5
Border.html<div jwcid="loginDialog"> <div class="dialog"> <form jwcid="form"> <p> Enter your email address and password to log in. </p> <span jwcid="errors@Errors"/> <label jwcid="@FieldLabel" field="component:email"/> <input jwcid="email" size="30"/> <label jwcid="@FieldLabel" field="component:password"/> <input jwcid="password" size="30"/> <input type="submit" value="Login"/> <input jwcid="@Cancel" ajax="true"/> </form> <p> Not registered yet? <a jwcid="register2"> Click here to setup an account</a>. </p> </div></div>
6
Border.html
Placeholder for tacos:Dialog component Dialogs are invisible until un-hidden Active dialogs mask the rest of the page
<div jwcid="loginDialog">
. . .
</div>
7
Border.jwc
Dialog visibility from dialogHidden property of page
Login link: Set dialogHidden to false Re-render just the loginDialog
<component id="loginDialog" type="tacos:Dialog"> <binding name="hidden" value="dialogHidden"/></component>
8
Border.jwc
Invoke the listener method Re-render just the loginDialog component
<component id="login" type="tacos:AjaxDirectLink"> <binding name="listener" value="listener:doShowLogin"/> <binding name="updateComponents" value="{ 'loginDialog' }"/></component>
9
Form component
Tapestry forms are inside a Form component: Unique names & ids for fields (even inside loops) Tracking of user input and errors Handling submit, refresh & cancel AjaxForm: Partial page refreshes
<form jwcid="form"> . . .</form>
10
Form Component
listener: Name of listener method to invoke delegate Object that tracks user input and
errors
<component id="form" type="tacos:AjaxForm"> <binding name="updateComponents" value="{ 'errors' }"/> <binding name="success" value="listener:doLogin"/> <binding name="cancel" value="listener:doCancel"/> <binding name="delegate" value="delegate"/></component>
11
OGNL
Object Graph Navigation Language Expression language used by Tapestry ognl: prefix when used in HTML template No prefix when used in XML file
12
OGNL
Simple: property namesdelegate getDelegate(),
setDelegate() Complex: property paths
poll.title getPoll().getTitle(), getPoll().setTitle()
Much more!
13
TextField Component
@FieldLabel anonymous component of type FieldLabel Nothing in the Login.page file
component:email reference to email component
<label jwcid="@FieldLabel" field="component:email"/>:<input jwcid="email" size="30"/>
14
TextField Component
value property to read and update validators validations to perform displayName Used in error messages, and
by FieldLabel
<component id="email" type="TextField"> <binding name="value" value="email"/> <binding name="validators" value="validators:required"/> <binding name="displayName" value="message:email-label"/></component>
15
Password TextField
<component id="password" type="TextField"> <binding name="value" value="password"/> <binding name="validators" value="validators:required"/> <binding name="displayName" value="message:password-label"/> <binding name="hidden" value="true"/></component>
16
Register Link
literal: value is just a string, not an OGNL expression
<p>Not registered yet? <a jwcid="register">Click here to setup an account</a>. </p>
<component id="register" type="PageLink"> <binding name="page" value="literal:Register"/> </component>
17
Java Classpublic abstract class Border extends BaseComponent{
public abstract String getEmail();
public abstract String getPassword();
. . .
18
Abstract?
Pages are stateful Hold transient data during request Hold persistent data between requests
Pages are expensive to create Pages are pooled
Like database connections
19
No, Really, Abstract?
Tapestry extends abstract class Adds getter, setter, instance variables Adds end-of-request cleanup Lots of injections based on getters and
annotations (or XML)
20
Embedded Components
Injected CodeApplication
Code
Inherited Code
Page Behavior
21
Java Class
getEmail() & setEmail() getPassword() & setPassword()
public abstract class Border extends BaseComponent{
public abstract String getEmail();
public abstract String getPassword();
. . .
22
Listener Methods
Public method Changes server side state Form will re-render
public void doCancel(){ getLoginDialog().hide();}
23
Injecting Services
Service defined in HiveMind IoC Container Can inject Spring beans as easily Keep business logic out of pages / components
@InjectObject("service:epluribus.LoginAuthenticator")public abstract LoginAuthenticator getAuthenticator();
public interface LoginAuthenticator{ User authenticateCredentials(String email, String plaintextPassword);}
24
Listener Methodspublic String doLogin() { String email = getEmail(); String password = getPassword();
User user = getAuthenticator().authenticateCredentials( email, password);
. . .
25
Listener Methods . . .
if (user == null) { getDelegate().record(null, "Invalid user name or password."); return null; }
getIdentity().login(user);
getLoginDialog().hide();}
26
Server Side State: ASO
Application State Objects Global to all pages Stored in HttpSession Created on demand Defined in HiveMind Injected into pages or components
@InjectState("identity")public abstract Identity getIdentity();
27
Identity ASO
public class Identity implements Serializable { . . .
public boolean isLoggedIn() { . . . }
public void login(User user) { . . . } public void logout() { . . . }
public User getUser() { . . . }}
28
Loops, Tables and Actions
29
Home.html
<table class="data-grid" cellspacing="0" jwcid="polls"/>
<div jwcid="pollingEndColumnValue@Block"><span jwcid="@InsertDate" date="ognl:poll.pollingEnd"/></div>
<div jwcid="statusColumnValue@Block"><span jwcid="@Insert" value="ognl:responseCount"/><a jwcid="respond">Respond</a></div>
30
Home.page
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
31
Home.page
Defines a new property on page Alternately: create abstract property
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
32
Home.page
contrib: is name of tapestry-contrib.jar library
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
33
Home.page
source total list of Poll objects
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
34
Home.page
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
columns how to break a Poll object into columns
35
Home.properties
column-id : title : OGNL expression poll.title poll.questionCount null calculated elsewhere
!status status is not sortable
table-columns=\ title:Title:title, \ questions:Questions:questionCount, \ pollingEnd:End of Polling:pollingEnd, \ !status:Status:null
36
Home.page
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
row Property to update with each rendered row (each Poll)
37
Home.page
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
index Stores index into row (used to set row CSS class)
38
Home.page
<property name="rowIndex"/>
<component id="polls" type="contrib:Table"> <binding name="source" value="polls"/> <binding name="columns" value="message:table-columns"/> <binding name="row" value="poll"/> <binding name="index" value="rowIndex"/> <binding name="rowsClass"> rowIndex == 0 ? "first" : null </binding> </component>
rowsClass CSS class value for the <tr> Identify first row to change its formatting
39
Home.html
<table class="data-grid" cellspacing="0" jwcid="polls"/>
<div jwcid="pollingEndColumnValue@Block"><span jwcid="@InsertDate" date="ognl:poll.pollingEnd"/></div>
<div jwcid="statusColumnValue@Block"><span jwcid="@Insert" value="ognl:responseCount"/><a jwcid="respond">Respond</a></div>
40
Home.html
<table class="data-grid" cellspacing="0" jwcid="polls"/>
<div jwcid="pollingEndColumnValue@Block"><span jwcid="@InsertDate" date="ognl:poll.pollingEnd"/></div>
<div jwcid="statusColumnValue@Block"><span jwcid="@Insert" value="ognl:responseCount"/><a jwcid="respond">Respond</a></div>
41
Home.page
DirectLink invokes a listener method when clicked
Can pass parameters into the listener method
<component id="respond" type="DirectLink"> <binding name="listener" value="listener:doRespond"/> <binding name="parameters" value="poll.id"/></component>
42
Home.java
Parameters show up … with proper type (not just String)
@InjectPage("RespondToPoll")public abstract RespondToPoll getRespondToPoll();
public void doRespond(long pollId) { Poll poll = getPollAccess().getPoll(pollId);
// TODO: A few checks, i.e., Poll is active getRespondToPoll().activate(poll);}
43
Creating New Components
44
FCKEditor
"the text editor for the Internet" Open Source http://www.fckeditor.net/
45
FCKEditor
Primarily a JavaScript library: FCKeditor/fckeditor.js
Goal: Component to take place of TextArea
46
FCKEditor Component
FCKEditor.jwc Copy of TextArea.jwc FCKEditor extends TextArea
47
FCKEditor.javaprotected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle){ super.renderFormComponent(writer, cycle);
// Now, we want to work with the script.
PageRenderSupport support = TapestryUtils.getPageRenderSupport(cycle, this);
support.addExternalScript(getEditorScript().getResourceLocation());
String contextPath = getRequest().getContextPath();
String id = getClientId();
String clientObject = "editor_" + id;
StringBuffer buffer = new StringBuffer();
buffer.append(String.format("var %s = new FCKeditor('%s');\n", clientObject, id)); buffer.append(String.format("%s.BasePath = '%s/FCKeditor/';\n", clientObject, contextPath)); buffer.append(String.format("%s.ReplaceTextarea();\n", clientObject));
support.addInitializationScript(buffer.toString());}
48
FCKEditor.java
super.renderFormComponent(writer, cycle);
PageRenderSupport support = TapestryUtils.getPageRenderSupport(cycle, this);
support.addExternalScript( getEditorScript().getResourceLocation());
@Asset("context:FCKeditor/fckeditor.js")public abstract IAsset getEditorScript();
49
FCKEditor.java
String contextPath = getRequest().getContextPath();
String id = getClientId();
String clientObject = "editor_" + id;
50
FCKEditor.java
StringBuffer buffer = new StringBuffer();
buffer.append(String.format("var %s = new FCKeditor('%s');\n", clientObject, id));
buffer.append(String.format("%s.BasePath = '%s/FCKeditor/';\n", clientObject, contextPath));
buffer.append(String.format("%s.ReplaceTextarea();\n",
clientObject));
support.addInitializationScript(buffer.toString());
51
FCKEditor Summary
Easy to knit components & JavaScript Super easy to use:
<input jwcid="@FCKEditor" value="ognl:description"/>
52
Wrap Up
53
More Tapestry Topics
Persistent Page Properties Creating New Components Localization Packaging component libraries Integration with Hibernate and Spring Unit and Integration Testing Extending and Overriding Tapestry
54
ePluribus Source
Via Anonymous SVN:
http://svn.javaforge.com/svn/tapestry/epluribus/trunk
User: anonymous Password: anon
55
Links
Tapestry http://tapestry.apache.org
HiveMind http://jakarta.apache.org/hivemind/ http://hivemind.apache.org/
Tacos http://tacos.sf.net/
OGNL http://www.ognl.org/