Title: Tapestry Components for Web 2.0
1Tapestry Components for Web 2.0
- Howard Lewis Ship
- TWD Consulting, Inc.
- hlship_at_gmail.com
2What is Tapestry?
Application
Page
Page
Component
Component
Component
Component
Component
Component
3Controller
Model
View
Java Beans
HTML template
Dynamic HTML output
Links / Form Submissions
4Login Form
5Border.html
ltdiv jwcid"loginDialog"gt ltdiv class"dialog"gt
ltform jwcid"form"gt ltpgt Enter your email
address and password to log in. lt/pgt ltspan
jwcid"errors_at_Errors"/gt ltlabel
jwcid"_at_FieldLabel" field"componentemail"/gt
ltinput jwcid"email" size"30"/gt ltlabel
jwcid"_at_FieldLabel" field"componentpassword"/gt
ltinput jwcid"password" size"30"/gt
ltinput type"submit" value"Login"/gt ltinput
jwcid"_at_Cancel" ajax"true"/gt lt/formgt ltpgt
Not registered yet? lta jwcid"register2"gt
Click here to setup an accountlt/agt. lt/pgt
lt/divgt lt/divgt
6Border.html
ltdiv jwcid"loginDialog"gt . . . lt/divgt
- Placeholder for tacosDialog component
- Dialogs are invisible until un-hidden
- Active dialogs mask the rest of the page
7Border.jwc
ltcomponent id"loginDialog" type"tacosDialog"gt
ltbinding name"hidden" value"dialogHidden"/gt lt/c
omponentgt
- Dialog visibility from dialogHidden property of
page - Login link
- Set dialogHidden to false
- Re-render just the loginDialog
8Border.jwc
ltcomponent id"login" type"tacosAjaxDirectLink"gt
ltbinding name"listener" value"listenerdoShow
Login"/gt ltbinding name"updateComponents"
value" 'loginDialog' "/gt lt/componentgt
- Invoke the listener method
- Re-render just the loginDialog component
9Form component
ltform jwcid"form"gt . . . lt/formgt
- 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
10Form Component
ltcomponent id"form" type"tacosAjaxForm"gt
ltbinding name"updateComponents" value"
'errors' "/gt ltbinding name"success"
value"listenerdoLogin"/gt ltbinding
name"cancel" value"listenerdoCancel"/gt
ltbinding name"delegate" value"delegate"/gt lt/comp
onentgt
- listener ? Name of listener method to invoke
- delegate ? Object that tracks user input and
errors
11OGNL
- Object Graph Navigation Language
- Expression language used by Tapestry
- ognl prefix when used in HTML template
- No prefix when used in XML file
12OGNL
- Simple property names delegate ? getDelegate(),
setDelegate() - Complex property paths poll.title ?
getPoll().getTitle(), getPoll().setTitle() - Much more!
13TextField Component
ltlabel jwcid"_at_FieldLabel" field"componentemail"
/gt ltinput jwcid"email" size"30"/gt
- _at_FieldLabel ? anonymous component of type
FieldLabel - Nothing in the Login.page file
- componentemail ? reference to email component
14TextField Component
ltcomponent id"email" type"TextField"gt
ltbinding name"value" value"email"/gt ltbinding
name"validators" value"validatorsrequired"/gt
ltbinding name"displayName" value"messageemail-l
abel"/gt lt/componentgt
- value ? property to read and update
- validators ? validations to perform
- displayName ? Used in error messages, and by
FieldLabel
15Password TextField
ltcomponent id"password" type"TextField"gt
ltbinding name"value" value"password"/gt
ltbinding name"validators" value"validatorsrequi
red"/gt ltbinding name"displayName"
value"messagepassword-label"/gt ltbinding
name"hidden" value"true"/gt lt/componentgt
16Register Link
ltpgtNot registered yet? lta jwcid"register"gtClick
here to setup an accountlt/agt. lt/pgt
ltcomponent id"register" type"PageLink"gt
ltbinding name"page" value"literalRegister"/gt
lt/componentgt
- literal ? value is just a string, not an OGNL
expression
17Java Class
public abstract class Border extends
BaseComponent public abstract String
getEmail() public abstract String
getPassword() . . .
18Abstract?
- Pages are stateful
- Hold transient data during request
- Hold persistent data between requests
- Pages are expensive to create
- Pages are pooled
- Like database connections
19No, 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)
20Page Behavior
21Java Class
public abstract class Border extends
BaseComponent public abstract String
getEmail() public abstract String
getPassword() . . .
- getEmail() setEmail()
- getPassword() setPassword()
22Listener Methods
public void doCancel() getLoginDialog().hide(
)
- Public method
- Changes server side state
- Form will re-render
23Injecting Services
_at_InjectObject("serviceepluribus.LoginAuthenticato
r") public abstract LoginAuthenticator
getAuthenticator()
public interface LoginAuthenticator User
authenticateCredentials(String email, String
plaintextPassword)
- Service defined in HiveMind IoC Container
- Can inject Spring beans as easily
- Keep business logic out of pages / components
24Listener Methods
public String doLogin() String email
getEmail() String password getPassword()
User user getAuthenticator().authenticateCredent
ials( email, password) . . .
25Listener Methods
. . . if (user null)
getDelegate().record(null, "Invalid user
name or password.") return null
getIdentity().login(user) getLoginDialog().hid
e()
26Server Side State ASO
_at_InjectState("identity") public abstract Identity
getIdentity()
- Application State Objects
- Global to all pages
- Stored in HttpSession
- Created on demand
- Defined in HiveMind
- Injected into pages or components
27Identity ASO
public class Identity implements Serializable
. . . public boolean isLoggedIn() . . .
public void login(User user) . . .
public void logout() . . .
public User getUser() . . .
28Loops, Tables and Actions
29Home.html
lttable class"data-grid" cellspacing"0"
jwcid"polls"/gt ltdiv jwcid"pollingEndColumnValu
e_at_Block"gt ltspan jwcid"_at_InsertDate"
date"ognlpoll.pollingEnd"/gt lt/divgt ltdiv
jwcid"statusColumnValue_at_Block"gt ltspan
jwcid"_at_Insert" value"ognlresponseCount"/gt lta
jwcid"respond"gtRespondlt/agt lt/divgt
30Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
31Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
- Defines a new property on page
- Alternately create abstract property
32Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
- contrib is name of tapestry-contrib.jar library
33Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
- source ? total list of Poll objects
34Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
- columns ? how to break a Poll object into columns
35Home.properties
table-columns\ titleTitletitle, \
questionsQuestionsquestionCount, \
pollingEndEnd of PollingpollingEnd, \
!statusStatusnull
- column-id title OGNL expression
- poll.title
- poll.questionCount
- null ? calculated elsewhere
- !status ? status is not sortable
36Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
- row ? Property to update with each rendered row
(each Poll)
37Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
- index ? Stores index into row (used to set row
CSS class)
38Home.page
ltproperty name"rowIndex"/gt ltcomponent
id"polls" type"contribTable"gt
ltbinding name"source" value"polls"/gt
ltbinding name"columns" value"messagetable-colum
ns"/gt ltbinding name"row" value"poll"/gt
ltbinding name"index" value"rowIndex"/gt
ltbinding name"rowsClass"gt rowIndex 0 ?
"first" null lt/bindinggt lt/componentgt
- rowsClass ? CSS class value for the lttrgt
- Identify first row to change its formatting
39Home.html
lttable class"data-grid" cellspacing"0"
jwcid"polls"/gt ltdiv jwcid"pollingEndColumnValu
e_at_Block"gt ltspan jwcid"_at_InsertDate"
date"ognlpoll.pollingEnd"/gt lt/divgt ltdiv
jwcid"statusColumnValue_at_Block"gt ltspan
jwcid"_at_Insert" value"ognlresponseCount"/gt lta
jwcid"respond"gtRespondlt/agt lt/divgt
40Home.html
lttable class"data-grid" cellspacing"0"
jwcid"polls"/gt ltdiv jwcid"pollingEndColumnValu
e_at_Block"gt ltspan jwcid"_at_InsertDate"
date"ognlpoll.pollingEnd"/gt lt/divgt ltdiv
jwcid"statusColumnValue_at_Block"gt ltspan
jwcid"_at_Insert" value"ognlresponseCount"/gt lta
jwcid"respond"gtRespondlt/agt lt/divgt
41Home.page
ltcomponent id"respond" type"DirectLink"gt
ltbinding name"listener" value"listenerdoRespond
"/gt ltbinding name"parameters"
value"poll.id"/gt lt/componentgt
- DirectLink invokes a listener method when clicked
- Can pass parameters into the listener method
42Home.java
_at_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)
- Parameters show up with proper type (not just
String)
43Creating New Components
44FCKEditor
- "the text editor for the Internet"
- Open Source
- http//www.fckeditor.net/
45FCKEditor
- Primarily a JavaScript library
- FCKeditor/fckeditor.js
- Goal
- Component to take place of TextArea
46FCKEditor Component
- FCKEditor.jwc ? Copy of TextArea.jwc
- FCKEditor extends TextArea
47FCKEditor.java
protected void renderFormComponent(IMarkupWriter
writer, IRequestCycle cycle)
super.renderFormComponent(writer, cycle) //
Now, we want to work with the script.
PageRenderSupport support TapestryUtils.getPageR
enderSupport(cycle, this) support.addExternalS
cript(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.R
eplaceTextarea()\n", clientObject))
support.addInitializationScript(buffer.toString())
48FCKEditor.java
super.renderFormComponent(writer,
cycle) PageRenderSupport support
TapestryUtils.getPageRenderSupport(cycle,
this) support.addExternalScript(
getEditorScript().getResourceLocation())
_at_Asset("contextFCKeditor/fckeditor.js") public
abstract IAsset getEditorScript()
49FCKEditor.java
String contextPath getRequest().getContextPath()
String id getClientId() String
clientObject "editor_" id
50FCKEditor.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.R
eplaceTextarea()\n", clientObject)) support.ad
dInitializationScript(buffer.toString())
51FCKEditor Summary
- Easy to knit components JavaScript
- Super easy to useltinput jwcid"_at_FCKEditor"
value"ognldescription"/gt
52Wrap Up
53More 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
54ePluribus Source
- Via Anonymous SVN
- http//svn.javaforge.com/svn/tapestry/epluribus/tr
unk - User anonymous
- Password anon
55Links
- 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/