0% found this document useful (0 votes)
70 views

Tapestry Module

Tapestry is an open-source framework for building dynamic, robust, and highly scalable Java web applications. It handles concerns like URL dispatching, state management, validation, localization, and exceptions. Applications are built with HTML templates combined with small amounts of Java code. Tapestry supports scaling from small single-page apps to large ones with hundreds of pages built by teams.

Uploaded by

rnp
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
70 views

Tapestry Module

Tapestry is an open-source framework for building dynamic, robust, and highly scalable Java web applications. It handles concerns like URL dispatching, state management, validation, localization, and exceptions. Applications are built with HTML templates combined with small amounts of Java code. Tapestry supports scaling from small single-page apps to large ones with hundreds of pages built by teams.

Uploaded by

rnp
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 51

Tapestry

IntroductiontoTapestry5

preparedby:

Enabling electronic transactions and bringing information closer to your customers


Module1
TappestryIntroduction
WhatisTapestry?

Tapestryisanopensourceframeworkforcreatingdynamic,robust,highly
scalablewebapplicationsinJava.Tapestrycomplementsandbuildsuponthe
standardJavaServletAPI,andsoitworksinanyservletcontainerorapplication
server.

Tapestrydividesawebapplicationintoasetofpages,eachconstructedfrom
components.Thisprovidesaconsistentstructure,allowingtheTapestry
frameworktoassumeresponsibilityforkeyconcernssuchasURLconstruction
anddispatch,persistentstatestorageontheclientorontheserver,userinput
validation,localization/internationalization,andexceptionreporting.Developing
TapestryapplicationsinvolvescreatingHTMLtemplatesusingplainHTML,and
combiningthetemplateswithsmallamountsofJavacode.InTapestry,you
createyourapplicationintermsofobjects,andthemethodsandpropertiesof
thoseobjectsandspecificallynotintermsofURLsandqueryparameters.
TapestrybringstrueobjectorienteddevelopmenttoJavawebapplications.

Tapestryisspecificallydesignedtomakecreatingnewcomponentsveryeasy,as
thisisaroutineapproachwhenbuildingapplications.

Tapestryisarchitectedtoscalefromtiny,singlepageapplicationsallthewayup
tomassiveapplicationsconsistingofhundredsofindividualpages,developedby
large,diverseteams.Tapestryeasilyintegrateswithanykindofbackend,
includingJEE,HiveMind,SpringandHibernate.

It'smorethanwhatyoucandowithTapestry...it'salsohowyoudoit!Tapestryis
avastlyproductiveenvironment.Javadevelopersloveitbecausetheycanmake
Javacodechangesandseethemimmediately...noredeploy,norestart!Andit's
blazinglyfasttoboot(evenwhenfileschange).Designersloveitbecause
TapestrytemplatesaresoclosetoordinaryHTML,withoutallthecruftand
confusionseeninJavaServerPages.Managersloveitbecauseitmakesiteasy
forlargeteamstoworktogether,andbecausetheyknowimportantfeatures
(includinglocalization)arebakedrightin.OnceyouworkinTapestrythere'sno
goingback!

Tapestryitselfisbrokenintoseveralmodules:

tapestryannotations AfewTapestryannotationsthatmaybe
usedwithnoncomponentclasses.
tapestrycore Thecoreimplementationofthe
Tapestryframework,includingallthe

2/51
primarybuiltincomponents.
tapestryhibernate IntegrationwiththeHibernate
Object/RelationalMappingframework.
tapestryioc TheTapestryInversionofControl
Container.
tapestryspring IntegrationwithSpring.
tapestrytest Tapestrytestutilities.
tapestryupload Tapestryfileuploadcomponent.

3/51
Module2

SettingUpYourEnvironment
Rightnow,wemustfirsttalkaboutourdevelopmentenvironment.Thejoyandthe
painofJavadevelopmentisthevolumeofchoiceavailable.There'sjusta
numberofJDKs,IDEsandotherTLA1soutthere.
Let'stalkaboutastackoftools,allopensourceandfreelyavailable,thatyou'll
needtosetup.Likelyyouhavesomeofthese,orsomeversionofthese,already
onyourdevelopmentmachine.

JDK1.5
Tapestry5makesuseoffeaturesofJDK1.5.ThisincludesJavaAnnotations,
andalittlebitofJavaGenerics.

Eclipse3.3
Sincewe'reemphasizingafreeandopensourcestack,we'llconcentrateonthe
bestfreeIDE.Eclipse3.3comesinvariousflavors,andincludesareasonable
XMLeditorbuiltin.

Jetty5.1
JettyisanopensourceservletcontainercreatedbyGregWilkinsofWebtide
(whichofferscommercialsupportforJetty).Jettyishighperformanceand
designedforeasyembeddinginothersoftware.I'vechosenthe5.1release,
ratherthanthecuttingedgeJetty6,becauseitiscompatiblewithJettyLauncher
(seebelow).

JettyLauncher
JettyLauncherisapluginforEclipsethatmakesiteasytolaunchJetty
applicationsfromwithinEclipse.Thisisagreatmodel,sinceyoucanrunor
debugdirectlyfromyourworkspacewithoutwastingtimepackagingand
deploying.

Maven2.0.7
Mavenisasoftwarebuildtoolofratherepicambitions.Ithasaverysophisticated
pluginsystemthatallowsittodovirtuallyanything,thoughcompilingJavacode,
buildingWARandJARfiles,andcreatingreportsandwebsitesareitsforte.
PerhapsthebiggestadvantageofMavenover,say,Ant,isthatitcandownload
projectdependencies(suchastheTapestryJARfiles,andtheJARfilesTapestry
itselfdependson)automaticallyforyou,fromoneofseveralcentralrepositories.

4/51
MavenPlugin
TheMavenPluginforEclipseintegratesMavenandEclipse.Itincludessome
featuresforeditingthepom.xml(theMavenprojectdescriptionfilewhich
identifies,amongmanyotherthings,whatJARfilesareneededbytheproject).
Moreimportantly,aMavenenabledprojectautomaticallystayssynchronizedwith
thePOM,automaticallylinkingEclipseprojectclasspathtofilesfromthelocal
Mavenrepository.

Tapestry5.0.x
Youshouldnothavetodownloadthisdirectly;aswe'llsee,Mavenshouldtake
careofdownloadingTapestry,anditsdependencies,asneeded.

Toeasesettingupyourdevelopmentenvironment,wehaveprovidesourceofthe
softwares/toolsthatwillbeusedinthismodule,makesureyouhaveitand,do
thesesteps

1. JDK1.5(installinc:\java\)
%SOURCE%\Java\J2SE\jdk1_5_0_07windowsi586p.exe

2. Eclipse3.2(extracttoc:\)
%SOURCE%\Editor\Eclipse\wtpallinonesdkR1.5.1200609230508
win32.zip

3. Maven(extracttoc:\)
%SOURCE%\Java\Tools\maven2.0.7bin.zip

4. Jetty(extracttoc:\)
%SOURCE%\Jetty\jetty5.1.14.tgz

5. InstallPluginMavenonEclipse,chooseHelp|SoftwareUpdates|Findand
Install\Searchfornewfeaturestoinstall\NewArchivedSite
%SOURCE%\Editor\Eclipse\plugin\mavenplugin0.0.10.zip

6. InstallPluginSubversiononEclipse,chooseHelp|SoftwareUpdates|Find
andInstall\Searchfornewfeaturestoinstall\NewArchivedSite
%SOURCE%\Editor\Eclipse\plugin\site1.0.5.zip

7. InstallPluginJettyonEclipse,chooseHelp|SoftwareUpdates|Findand
Install\Searchfornewfeaturestoinstall\NewArchivedSite
%SOURCE%\Editor\Eclipse\plugin\JettyLauncher1.4.1.zip

8. SetpathenvironmentvariabletoaddMavenbinfolder
Setpath=%path%;c:\maven2.0.7\bin

5/51
Module3
YourFirstTapestryApplication
Beforewecangetdowntothefun,wehavetocreateanemptyapplication.
TapestryusesafeatureofMaventodothis:archetypes(atoocleverwayof
saying"projecttemplates").

Whatwe'lldoiscreateanemptyshellapplicationusingMaven,thenimportthe
applicationintoEclipsetodotherestofthework.

Beforeproceeding,wehavetodecideonthreethings:AMavengroupidand
artifactidforourprojectandarootpackagename.

Mavenusesthegroupidandartifactidtoprovideauniqueidentityforthe
application,andTapestryneedstohaveabasepackagenamesoitknowswhere
tolookforpagesandcomponents.

Forthisexample,we'llusethegroupidorg.apache.tapestryandtheartifactid
tapestrytutorial1,andwe'lluseorg.apache.tapestry.tutorialasthebase
package.

mvnarchetype:create
DarchetypeGroupId=org.apache.tapestry
DarchetypeArtifactId=quickstart
DarchetypeVersion=5.0.5
DgroupId=org.apache.tapestry
DartifactId=tutorial1
DpackageName=org.apache.tapestry.tutorial

Notes:We'veshownthisasseverallines,butitwouldnormallybeenteredasasinglelong
line.

Asacommand,it'squiteadoozy!Ifyouaregoingtocreatelotsofprojects,
creatingawrapperscriptforthisisasmartidea.

Executethisinatemporarydirectory,itwillcreateasubdirectory:tapestry
tutorial1.

Thefirsttimeyouexecutethiscommand,Mavenwillspendquiteawhile
downloadingallkindsofJARsintoyourlocalrepository,whichcantakeaminute
ormore.Later,onceallthatisalreadyavailablelocally,thewholecommand
executesinunderasecond.

OneofthefirstthingsyoucandoisuseMaventorunJettydirectly.

Changeintothenewlycreateddirectory,andexecutethecommand:

mvnjetty:run

6/51
Again,thefirsttime,there'sadizzyingnumberofdownloads,butbeforeyou
knowit,theJettyservletcontainerisupandrunning.

Youcanopenawebbrowsertohttp://localhost:8080/tutorial1/toseetherunning
application:

Thedateandtimeinthemiddleofthepageprovesthatthisisaliveapplication.

Let'slookatwhatMavenhasgeneratedforus.Todothis,we'regoingtoloadthe
projectinsideEclipseandcontinuefromthere.

StartbyhittingControlCintheTerminalwindowtoclosedownJetty..

LaunchEclipseandswitchovertotheJavaBrowserPerspective.

RightclickinsidetheProjectsviewandselectImport...

Choosethe"existingprojects"option:

NowselectthefoldercreatedbyMaven:

7/51
WhenyouclicktheFinishbutton,theprojectwillbeimportedintotheEclipse
workspace.

Mavendictatesthelayoutoftheproject:

Javasourcefilesundersrc/main/java
Webapplicationfilesundersrc/main/webapp(including
src/main/webapp/WEBINF)
Javatestsourcesundersrc/test/java
Noncoderesourcesundersrc/main/resourcesandsrc/test/resources

(Tapestryusesanumberofnoncoderesources,suchastemplatefilesand
messagecatalogs,whichwillultimatelybepackagedintotheWARfile.)

TheMavenPlugin,insideEclipse,hasfoundallthereferencedlibrariesinyour
localMavenrepository,andcompiledthetwoclassescreatedbyquickstart
archetype.

Let'slookatwhatthearchetypehascreatedforus,startingwiththeweb.xmlfile:

src/main/webapp/WEBINF/web.xml
<?xmlversion="1.0"encoding="UTF8"?>
<!DOCTYPEwebapp
PUBLIC"//SunMicrosystems,Inc.//DTDWebApplication2.3//EN"
"http://java.sun.com/dtd/webapp_2_3.dtd">
<webapp>
<displayname>tutorial1Tapestry5Application</displayname>
<contextparam>
<!TheonlysignificantconfigurationforTapestry5,thisinformsTapestry
ofwheretolookforpages,componentsandmixins.>
<paramname>tapestry.apppackage</paramname>
<paramvalue>org.apache.tapestry.tutorial</paramvalue>
</contextparam>
<filter>

8/51
<filtername>app</filtername>
<filterclass>org.apache.tapestry.TapestryFilter</filterclass>
</filter>
<filtermapping>
<filtername>app</filtername>
<urlpattern>/*</urlpattern>
</filtermapping>
</webapp>

Thisisshortandsweet:oucanseethatthepackagenameyouprovidedearlier
showsupasthetapestry.apppackagecontextparameter;theTapestryFilter
instancewillusethisinformationtolocatetheJavaclasseswe'lllookatnext.

Tapestry5operatesasaservletfilterratherthanasatraditionalservlet.Inthis
way,Tapestryhasachancetointerceptallincomingrequests,todeterminewhich
onesapplytoTapestrypages(orotherresources).Theneteffectisthatyoudon't
havetomaintainanyadditionalconfigurationforTapestrytooperate,regardless
ofhowmanypagesorcomponentsyouaddtoyourapplication.

TapestryhasaspecialcaseforaURLthatspecifiesthehostandthecontext
("/tutorial1"inthiscase)butnothingelse...itrenderstheStartpageofthe
application.Sofar,theStartpageistheonlypageintheapplication.Let'ssee
whatitlookslike.

TapestrypagesminimallyconsistofanordinaryJavaclassplusacomponent
templatefile.

Let'sstartwiththetemplate,whichisstoredinthewebapp'sWEBINFfolder.
TapestrycomponenttemplatesarewellformedXMLdocuments.Thismeansthat
youcanuseanyavailableXMLeditor.TemplatesmayevenhaveaDOCTYPEor
anXMLschematovalidatethestructureofthetemplate.Thatis,yourbuild
processmayuseatooltovalidateyourtemplates.Atruntime,whenTapestry
readsthetemplate,itdoesnotuseavalidatingparser.Forthemostpart,the
templatelookslikeordinaryXHTML:

src/main/webapp/Start.tml:
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>tutorial1StartPage</title>
</head>
<body>
<h1>tutorial1StartPage</h1>

<p>Thisisthestartpageforthisapplication,agoodplacetostart
yourmodifications.
Justtoprovethisislive:</p>

<p>Thecurrenttimeis:${currentTime}.</p>

<p>
[<t:pagelinkt:page="Start">refresh</t:pagelink>]
</p>
</body>
</html>

9/51
ThegoalinTapestryisforcomponenttemplates,suchasStart.html,tolookas
muchaspossiblylikeordinary,staticHTMLfiles.Bystatic,wemeanunchanging,
asopposedtoadynamicallygeneratedTapestrypage.Infact,theexpectationis
thatinmanycases,thetemplateswillstartasstaticHTMLfiles,createdbyaweb
developerandthenbeinstrumentedtoactasliveTapestrypages.

TapestryhidesnonstandardelementandattributesinsidetheXMLnamespace.
Byconvention,theprefix"t:"isusedforthisnamespace,butthatisnota
requirement.

ThereonlytwobitsofTapestryinstrumentationonthispage.

Firstisthewaywedisplaythecurrentdateandtime:${currentTime}.This
syntaxisusedtoaccessapropertyofthepageobject,apropertynamed
currentTime.Tapestrycallsthisanexpansion.Thevalueinsidethebracesisthe
nameofastandardJavaBeanspropertysuppliedbythepage.Aswe'llseein
laterchapters,thisisjustthetipoftheicebergforwhatispossibleusing
expansions.

Theotheritemisthelinkusedtorefreshthepage.We'respecifyingacomponent
asanXMLelementwithintheTapestrynamespace.Theelementname,
"pagelink",definesthetypeofcomponent.PageLink(Tapestryiscase
insensitive)isacomponentbuiltintotheframework;itispartofthecore
componentlibrary.Theattribute,page,isastringthenameofthepagetolink
to.Here,we'relinkingbacktothesamepage,page"Start".

ThisishowTapestryworks;theStartpagecontainsaninstanceofthePageLink
component.ThePageLinkcomponentisconfiguredviaitsparameters,which
controlswhatitdoesandhowitbehaves.

TheURLthatthePageLinkcomponentwillrenderoutis
http://localhost:8080/tutorial1/start.Tapestryiscaseinsensitive
(http://localhost:8080/tutorial1/STARTwouldworkjustaswell),and
generateslowercaseURLsbecausethosearemorevisuallypleasing.The
servletcontainerisnotsoforgiving,andexpectsanexactmatchonthecontext
nameportionoftheURL:"/tutorial1".

Tapestryignorescasewhereeveritcan.Insidethetemplate,weconfiguredthe
PageLinkcomponent'spageparameterwiththenameofthepage,"Start".Here
toowecouldbeinexactoncase.Feelfreetouse"start"ifthatworksforyou.

Notes:Youdohavetonameyourcomponenttemplatefile,Start.html,withtheexactsame
caseasthecomponentclassname,Start.Ifyougetthecasewrong,itmayworkonsome
operatingsystems(suchasWindows)andnotonothers(MacOSX,Linux,andmostothers).
Thiscanbereallyvexing,asitiscommontodeveloponWindowsanddeployonLinuxor
Solaris,sobecarefulaboutcaseinthisonearea.

10/51
Clickingthelinkinthewebbrowsersendsarequesttorerenderthepage;the
templateandJavaobjectarereusedtogeneratetheHTMLsenttothebrowser,
whichresultsintheupdatedtimeshowingupinthewebbrowser.

ThefinalpieceofthepuzzleistheJavaclassforthepage.Tapestryhasvery
specificrulesforwherepageclassesgo.Rememberthepackagename
(configuredinsideweb.xml)?Tapestryaddsasubpackage,"pages",toitandthe
Javaclassgoesthere.ThusthefullJavaclassnameis
org.apache.tapestry.tutorial.pages.Start.

src/main/java/org/apache/tapestry/tutorial/pages/Start.java
packageorg.apache.tapestry.tutorial.pages;

importjava.util.Date;

/**
*Startpageofapplicationtutorial1.
*/
publicclassStart
{
publicDategetCurrentTime()
{
returnnewDate();
}
}

That'sprettydarnsimple:Noclassestoextend,nointerfacestoimplement,justa
verypurePOJO(PlainOldJavaObject).YoudohavetomeetheTapestry
frameworkhalfway:

YouneedtoputtheJavaclassintheexpectedpackage,
org.apache.tapestry.tutorial.pages
Theclassmustbepublic
Youneedtomakesurethere'sapublic,noargumentsconstructor(here,
theJavacompilerhassilentlyprovidedoneforus)

ThetemplatereferencedthepropertycurrentTimeandwe'reprovidingthatasa
property,asasyntheticproperty,apropertythatiscomputedonthefly(rather
thanstoredinaninstancevariable).

Thismeansthateverytimethepagerenders,afreshDateinstanceiscreated,
whichisjustwhatwewant.

Asthepagerenders,itgeneratestheHTMLmarkupthatissenttotheclientweb
browser.Formostofthepage,thatmarkupisexactlywhatcameoutofthe
componenttemplate:thisiscalledthestaticcontent(we'reusingtheterm"static"
tomean"unchanging").

Theexpansion,${currentTime},isdynamic:differenteverytime.Tapestrywill
readthatpropertyandconverttheresultintoastring,andthatstringismixedinto
thestreamofmarkupsenttotheclient.We'lloftentalkaboutthe"client"andwe
don'tmeanthepeopleyousendyourinvoicesto:we'retalkingabouttheclient

11/51
webbrowser.Ofcourse,inaworldofwebspidersandotherscreenscrapers,
there'snoguaranteethatthethingontheotherendoftheHTTPpipeisreallya
webbrowser.You'lloftenseelowlevelHTMLandHTTPdocumentationtalk
aboutthe"useragent".Likewise,thePageLinkcomponentisdynamic,inthatit
generatesaURLthatis(potentially)differenteverytime.

TapestryfollowstherulesdefinedbySun'sJavaBeansspecification:aproperty
nameofcurrentTimemapstotwomethods:getCurrentTime()and
setCurrentTime().Ifyouomitoneoftheotherofthesemethods,thepropertyis
eitherreadonly(ashere),orwriteonly.

Tapestrydoesgoonestepfurther:itignorescasewhenmatchingproperties
insidetheexpansiontopropertiesofthepage.Inthetemplatewecouldsay
${currenttime}or${CurrentTime}oranyvariation,andTapestrywillstillinvoke
thegetCurrentTime()method.

Inthenextchapter,we'llstarttobuildasimplehiloguessinggame,butwe'vegot
onemoretaskbeforethen,plusamagictrick.

ThetaskistosetupJettytorunourapplicationdirectlyoutofourEclipse
workspace.Thisisagreatwaytodevelopwebapplications,sincewedon'twant
tohavetouseMaventocompileandruntheapplication...orworseyet,use
Maventopackageanddeploytheapplication.That'sforlater,whenwewantto
puttheapplicationintoproduction.Fordevelopment,wewantafast,agile
environmentthatcankeepupwithourchanges,andthatmeanswecan'twaitfor
redeploysandrestarts.

12/51
ChoosetheRun...itemfromtheEclipseRunmenutogetthelaunch
configurationdialog:

SelectJettyWebandclicktheNewbutton:

We'vefilledinanameforourlaunchconfiguration,andidentifiedtheproject.
We'vealsotoldJettyLauncherwhereourJettyinstallationis.We'veidentifiedthe

13/51
webcontextassrc/main/webapp,andwe'veturnedonNCSAloggingfor
goodmeasure.

Inaddition,we'vesetupthecontextas"/tutorial1",whichmatcheswhatour
eventualWARfile,tutorial1.war,wouldbedeployedasinsideanapplication
server.

OnceyouclickRun,Jettywillstartupandlaunch(itshouldtakeabouttwo
seconds).

YoumaynowstarttheapplicationwiththeURLhttp://localhost:8080/tutorial1/.

Nowit'stimeforthemagictrick.EditStart.javaandchangethegetCurrentTime()
methodto:

publicStringgetCurrentTime()
{
return"AgreatdaytolearnTapestry";
}

Makesureyousavechanges;thenclicktherefreshlinkinthewebbrowser:

ThisoneofTapestry'searlywowfactorfeatures:changestoyourcomponent
classesarepickedupimmediately.Norestart.Noredeploy.Makethechanges
andseethemnow.Nothingshouldslowyoudownorgetinthewayofyougetting
ourjobdone.

Nowthatwehaveourbasicapplicationsetup,andreadytorun(ordebug)
directlyinsideEclipse,wecanstartworkingonimplementingourHi/Logamein
earnest.

14/51
Module4
ImplementingtheHi/LoGuessingGame
Let'sstartbuildingtheHi/LoGuessinggame.

Inthegame,thecomputerselectsanumberbetween1and10.Youtryand
guessthenumber,clickinglinks.Attheend,thecomputertellsyouhowmany
guessesyourequired.

We'llbuilditinsmallpieces,usingthekindofiterativedevelopmentthatTapestry
makessoeasy.

src/main/webapp/Start.tml:
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>tutorial1StartPage</title>
</head>
<body>

<h1>Hi/LoGuess</h1>

<p>I'mthinkingofanumberbetweenoneandten...</p>

<p>
<t:actionlink>Startguessing</t:actionlink>
</p>

</body>
</html>

Herewe'vetakenthetemplatecreatedbythequickstartarchetypeandchanged
itaroundtofitourneeds.TheActionLinkcomponentwillcreatealinkthatwill
triggeramethodinsideourJavaclass.Youcanlaunchtheapplicationtotryit
out:

15/51
However,clickingthelinkdoesn'tdoanythingyet.Wehaven'ttoldTapestrywhat
todowhenthelinkgetsclicked.

Let'sfixthat.We'llchangetheStartclasssothatitwillreactwhenthelinkis
clicked...butwhatshoulditdo?Well,tostarttheguessingprocess,weneedto
comeupwitharandomnumber(betweenoneandten).Weneedtotellthe
Guesspageaboutthatnumber,andweneedtomakesuretheGuesspageis
starteduptodisplaytheresponse.

First,theGuesspage.Justtogetstarted,we'llcreateaGuesspagewithout
muchguessing:it'lljustshowusthetargetnumber,thenumberwe'resupposed
tobeguessing.

src/main/webapp/Guess.tml:
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>GuessANumber</title>
</head>
<body>

<h1>Thetargetnumberis${target}.</h1>

</body>
</html>

OntheJavaside,theGuesspageneedstohaveatargetproperty:

packageorg.apache.tapestry.tutorial.pages;

publicclassGuess
{
privateint_target;

voidsetup(inttarget)
{
_target=target;
}
}

16/51
Thekeymethodhereissetup():ItisinvokedtotelltheGuesspagewhatthe
targetnumberis.Noticethatisispackageprivate,notpublic;itisonlyexpected
tobeinvokedfromtheStartpage(aswe'llseeinamoment),sothere'snoneed
tomakeitpublic.Laterwe'llseethatthere'smoresetupthanjustinitializingthe
_targetinstancevariable(whichiswhywedon'tnamethemethodsetTarget()).

Thenamingconvention,usingleadingunderscoresforfields,isNOTa
requirementofTapestry;that'sjustmypersonalcodingstyle.

NowwecanmovebacktotheStartpage.Whatwewantistohavethe
ActionLinkcomponentinvokeamethodonourpage.Wecanthengeneratea
randomtargetnumber.We'lltelltheGuesspagewhatthetargetnumberisand
thenmakesureisthetheGuesspage,andnottheStartpage,thatrendersthe
responseintotheuser'swebbrowser.That'sactuallyquiteafewconceptstotake
inallatonce.

Let'sstartwiththecode,andbreakitdown:

src/main/java/org/apache/tapestry/tutorial/pages/Start.java
packageorg.apache.tapestry.tutorial.pages;

importjava.util.Random;

importorg.apache.tapestry.annotations.InjectPage;

publicclassStart
{
privatefinalRandom_random=newRandom();

@InjectPage
privateGuess_guess;

ObjectonAction()
{
inttarget=_random.nextInt(10)+1;

_guess.setup(target);

return_guess;
}
}

Whatwe'retalkingabouthereiscommunicationofinformationfromtheStart
pagetotheGuesspage.Intraditionalservletdevelopment,thisisdoneina
bizarreway...storingattributesintothesharedHttpSessionobject.Ofcourse,for
thattowork,both(orall)partieshavetoagreeonthetypeofobjectstored,and
thewellknownnameusedtoaccesstheattribute.That'sthesourceofalarge
numberofbugs.It'salsonotveryobjectoriented...stateissomethingthat
shouldbeinsideobjects(andprivate),notoutsideobjects(andpublic).

TheTapestrywayisveryobjectoriented:everythingisdoneintermsofobjects
andmethodsandpropertiesofthoseobjects.

17/51
Thiscommunicationstartswiththeconnectionbetweenthetwopages:inthis
case,theInjectPageannotationallowsanotherpageintheapplicationtobe
injectedintotheStartpage.

Let'sseewhatwedowiththisinjectedpage.It'susedinsideonAction().You
mightguessthatthismethodisinvokedwhenthelink("Startguessing")is
clicked.Butwhy?

Thisisastrongexampleofconventionoverconfiguration.Tapestryhasanaming
conventionforcertainmethods:"onEventType[FromComponentId]".Here,the
eventtypeis"action"andthecomponentidisnotevenspecified.Thistranslates
to"whentheactioneventisfiredfromanycomponent,invokethismethod".

"Theactionevent?"ThisunderlinesabitabouthowTapestryprocessesrequests.
WhenyouclickalinkgeneratedbytheActionLinkcomponent,Tapestryisableto
identifytheunderlyingcomponentinsidetherequest:itknowsthatthe
componentisontheStartpage,anditknowsthecomponentwithinthepage.
Herewedidn'tgivetheActionLinkcomponentaspecificid,soTapestrysupplied
one.An"action"eventistriggeredinsidetheActionLinkcomponent,andthat
eventbubblesuptothepage,wheretheonAction()methodactsasanevent
handlermethod.

So...ActionLinkcomponent>actionrequest>onAction()eventhandler
method.

Eventhandlermethodsdon'thavetobepublic;theyareusuallypackageprivate
(asinthisexample).Also,itisn'tanerrorifarequestnevermatchesanevent
handler.BeforeweaddedtheonAction()eventhandler,that'sexactlywhat
happened;therequestpassedthroughwithoutanyeventhandlermatch,and
TapestrysimplyrerenderedtheStartpage.

Whatcanyoudoinsideaneventhandlermethod?Anykindofbusinesslogicyou
like;Tapestrydoesn'tcare.Herewe'reusingarandomnumbergeneratortoset
thetargetnumbertoguess.

WealsousetheinjectedGuesspage;weinvokethesetup()methodtotellit
aboutthenumbertheuseristryingtoguess.

Thereturnvalueofaneventhandlermethodisveryimportant;thevaluereturned
informsTapestryaboutwhatpagewillrendertheresponsetotheclient.By
returningtheinjectedGuesspage,we'retellingTapestrythattheGuesspage
shouldbetheonetorendertheresponse.

Again,thisisabigdifferencebetweenTapestryandservlets(orStruts).Tapestry
tightlybindsthecontroller(theJavaclass)tothetemplate.UsingJSPs,you
wouldhaveextraconfigurationtoselectaview(usallybyalogicname,suchas
"success")toa"view"(aJSP).Tapestrycutsthroughallthatcruftforyou.

18/51
Inlaterchapters,we'llseeotherpossibilitiesbesidesreturningapageinstance
fromaneventhandlermethod.

Forthemoment,makesureallthechangesaresaved,andclickthe"Start
guessing"link.

Thismaynotquitebewhatyouwereexpecting...butitisausefuldigressioninto
oneofTapestry'smostimportantfeatures:feedback.

SomethingwaswrongwiththeGuesspage,andTapestryhasreportedtheerror
toyousothatyoucanmakeacorrection.

Here,therootproblemwasthatwedidn'tdefineagetTarget()methodintheStart
class.Ooops.DeepinsideTapestry,aRuntmeExceptionwasthrowntoexplain
this.

Asoftenhappensinframeworks,thatRuntimeExceptionwascaughtand
rethrownwrappedinsideanewexception,theTapestryException.Thisaddeda
bitmoredetailtotheexceptionmessage,andlinkedtheexceptiontoalocation.
Sincetheerroroccurredinsideacomponenttemplate,Tapestryisabletodisplay
thatportionofthetemplate,highlightingthelineinerror.

Ifyouscrolldown,you'llseethatafterthestacktrace,Tapestryprovidesawealth
ofinformationaboutthecurrentrequest,includingheadersandquery

19/51
parameters.ItalsodisplaysinformationstoredintheHttpSession(ifthesession
exists),andotherinformationthatmaybeofuse.

Ofcourse,inaproductionapplication,thisinformationcanbehidden!

Let'sfixthisproblem,byaddingthefollowingtotheGuessclass:

publicintgetTarget()
{
return_target;
}

Persistingdatabetweenrequests

Thatfixestheproblem,butintroducesanother:

Whyisthetargetnumberzero?Didn'twesetittoarandomvaluebetween1and
10?

AtissuehereisthehowTapestryorganizesrequests.Tapestryhastwomain
typesofrequests:actionrequestsandrenderrequests.Renderrequestsare
easy,theURLincludesjustthepagename,andthatpageisrenderedout.

Actionrequestsaremorecomplicated;theURLwillincludethenameofthepage
andtheidofthecomponentwithinthepage,andperhapsthetypeofevent.

Afteryoureventhandlermethodisexecuted,Tapestrydeterminewhatpagewill
rendertheresponse;aswe'veseen,thatisbasedonthereturnvalueoftheevent
handlermethod.

Tapestrydoesn't,however,rendertheresponsedirectly,thewaymostservlet
applicationswould;insteaditsendsaredirectURLtotheclientwebbrowser.The
URLisarenderrequestURLforthepagethatwillrendertheresponse.

Youmayhaveseenthisbefore.Itiscommonlycalledtheredirectafterpost
pattern.Mostoften,itisassociatedwithformsubmissions(andaswe'llseein
laterchapters,aformsubmissionisanothertypeofactionrequest).

20/51
Sowhydoesthataffectthetargetvalue?Attheendofanyrequest(actionor
render),Tapestrywill"cleanhouse",resettinganyinstancevariablesbacktotheir
initial,defaultvalues(usually,nullorzero).

ThiscleaningisverynecessarytothebasicwayTapestryoperates:pagesare
expensiveentitiestocreate;tooexpensivetocreatefresheachrequest,andtoo
largeandcomplicatedtostoreintheHttpSession.Tapestrypoolspages,using
andreusingtheminrequestafterrequest.

Forthedurationofasinglerequestfromasingleuser,apageinstanceisbound
totherequest.Itisonlyaccessibletotheonerequest.Otherrequestsmaybe
boundtootherinstancesofthesamepage.Thesamepageinstancewillbeused
forrequestafterrequest.

So,insidetheactionrequest,thecodeinsidetheonAction()eventhandler
methoddidcallthesetup()method,andavaluebetween1and10wasstoredin
the_targetinstancevariable.Butattheendofthatrequest,thevaluewaslost,
andinthesubsequentrenderrequestfortheGuesspage,thevaluewaszero.

Fortunately,itisveryeasytotranscendthisbehavior.We'lluseanannotation,
@Persist,ontheinstancevariable:

@Persist
privateint_target;

NowwecanusethebrowserbackbuttontoreturntotheStartpage,andclickthelinkagain.

Oneofthenicethingsaboutthisapproach,theuserofredirects,isthathittingthe
refreshbuttondoesnotchooseanewtargetnumber.ItsimplyredrawstheGuess
pagewiththetargetnumberpreviouslyselected.Inmanyservletapplications,the
URLwouldbefortheaction"choosearandomnumber"andrefreshingwouldre
executethataction.

Creatingguessablelinks

Nowit'stimetostartthegameinearnest.Wedon'twanttojusttelltheuserwhat
thetargetnumberis,wewanttomakethemguess,andwewanttotrackhow
manyattemptstheytake.

21/51
Whatwewantistocreate10links,andcombinethoselinkswithlogiconthe
serverside,aneventhandlermethod,thatcaninterpretwhatvaluetheuser
selected.

Let'sstartwiththoselinks.We'regoingtouseanewcomponent,Loop,toloop
overasetofvalues:

src/main/webapp/Guess.tml:
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>GuessANumber</title>
</head>
<body>

<p>Makeaguessbetweenoneandten:</p>

<t:loopsource="1..10"value="guess">
<t:actionlinkt:id="link"context="guess">${guess}</t:actionlink>
</t:loop>

</body>
</html>

TheLoopcomponent'ssourceattributeidentifiesthevaluestoloopover.Often
thisisalistorarray,butherethespecialspecialsyntax,"1..10"meansiterate
overthenumbersbetween1and10,inclusive.

Thevalueattributegetsassignedthecurrentitemfromtheloop.We'llusea
propertyoftheGuesspageasakindofscratchpadforthispurpose:

privateint_guess;

publicintgetGuess()
{
return_guess;
}

publicvoidsetGuess(intguess)
{
_guess=guess;
}

ThecontextparameteroftheActionLinkishowwegetextrainformationintothe
actionrequestURL.Thecontextcanbeasinglevalue,oranarrayorlistof
values.Thevaluesareconvertedtostringsandtackedontotheactionrequest
URL.Theendresultishttp://localhost:8080/tutorial1/guess.link/4.

Whatis"guess.link"?That'sthenameofthepage,"guess",andtheidofthe
component("link",asexplicitlysetwiththet:idattribute).

Now,tohandlethoseguesses.We'regoingtoaddaneventhandlermethodthat
getsinvokedwhenalinkisclicked.We'realsogoingtoaddanewproperty,
message,tostorethemessagethatsays"toohigh"or"toolow".

@Persist
privateString_message;

22/51
publicStringgetMessage()
{
return_message;
}

StringonActionFromLink(intguess)
{
if(guess==_target)return"GameOver";

if(guess<_target)
_message=String.format("%distoolow.",guess);
else
_message=String.format("%distoohigh.",guess);

returnnull;
}

Here'sthebignews:TapestrywillconvertthenumberfromtheURLbackintoan
integerautomatically,sothatitcanpassitintothismethodasaparameter.We
canthencomparetheguessfromtheusertothesecrettargetnumber.

WeneedtoupdatetheGuesspagetoactuallydisplaythemessage;thisisdone
byaddingthefollowing:

<p>${message}</p>

Thisistrulybarebonesand,whenmessageisnull,willoutputanempty<p>
element.Arealapplicationwoulddressthisupabitmore(usingCSSandthelike
tomakeitprettier).

WedoneedabasicGameOverpage.

src/main/webapp/GameOver.tml:
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>GameOver!</title>
</head>
<body>

<h1>GameOver</h1>

<p>Youguessedthesecretnumber!</p>

</body>
</html>

src/main/java/org/apache/tapestry/tutorial/pages/GameOver.java:
packageorg.apache.tapestry.tutorial.pages;

publicclassGameOver
{

Withthisinplace,wecanmakeguesses,andgetfeedbackfromtheapplication:

23/51
Countingthenumberofguesses

Itwouldbenicetoprovidesomefeedbackabouthowmanyguessestheuser
tooktofindthenumber.That'seasyenoughtodo.

FirstweupdateGuesstostorethenumberofguesses:

@Persist
privateint_count;

Wehaveacoupleofchangestomaketotheeventhandlermethod.Wewantto
communicatetotheGameOverpagetheguesscount;sowe'llinjectthe
GameOverpagesowecaninitializeit.
ObjectonActionFromLink(intguess)
{
_count++;

if(guess==_target)
{
_gameOver.setup(_count);
return_gameOver;
}

if(guess<_target)
_message=String.format("%distoolow.",guess);
else
_message=String.format("%distoohigh.",guess);

returnnull;
}

So,weupdatethecountbeforecomparingand,insteadofreturningthename
overtheGameOverpage,wereturntheconfiguredinstance.

Lastly,weneedtomakesomechangestotheGameOverclass.

src/main/java/org/apache/tapestry/tutorial/GameOver.java:
packageorg.apache.tapestry.tutorial.pages;

importorg.apache.tapestry.annotations.Persist;

publicclassGameOver
{
@Persist
privateint_count;

24/51
publicintgetCount()
{
return_count;
}

voidsetup(intcount)
{
_count=count;
}
}

src/main/webapp/GameOver.tml:
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>GameOver!</title>
</head>
<body>

<h1>GameOver</h1>

<p>Youguessedthesecretnumberin${count}guesses!</p>

</body>
</html>

Partingthoughts

Whatwe'vegoneafterhereistheTapestryway:pagesasclassesthatstore
internalstateandcommunicatewitheachother.We'vealsoseentheTapestry
developmentpattern:lotsofsimplesmallstepsthatleverageTapestry'sabilityto
reloadtemplatesandclassesonthefly.

We'vealsoseenhowTapestrystoresdataforus,sometimesinthesession(via
the@Persistannotation)andsometimesintheURL.

ThecodeiswonderfullyfreeofanythingrelatedtoHTTPortheJavaServletAPI.
We'recodingusingrealobjects,withtheirowninstancevariablesandinternal
state.

25/51
Module5
FormsinTapestry
Inthepreviouschapters,wesawhowTapestrycanhandlesimplelinks,even
linksthatpassinformationintheURL.Inthischapter,we'llseehowTapestrycan
dothesame,andquiteabitmore,forHTMLforms.

FormsupportinTapestryisdeepandrich,morethancanbecoveredinasingle
chapter.However,wecanshowthebasics,includingsomeverycommon
developmentpatterns.Togetstarted,let'screateasimpleaddressbook
application.

We'llstartwiththedata,asimpleobjecttostoretheinformationwe'llneed.By
convention,theseclassesgoinadatasubpackage.Unliketheuseofthepages
subpackage(forpagecomponentclasses),thisisnotenforcedbyTapestry;it's
justaconvention.

src/main/java/org/apache/tapestry/tutorial/data/Address.java:

packageorg.apache.tapestry.tutorial.data;

publicclassAddress
{
privateHonorific_honorific;

privateString_firstName;

privateString_lastName;

privateString_street1;

privateString_street2;

privateString_city;

privateString_state;

privateString_zip;

privateString_email;

privateString_phone;

publicStringgetCity()
{
return_city;
}

publicStringgetEmail()
{
return_email;
}

publicStringgetFirstName()
{
return_firstName;

26/51
}

publicHonorificgetHonorific()
{
return_honorific;
}

publicStringgetLastName()
{
return_lastName;
}

publicStringgetPhone()
{
return_phone;
}

publicStringgetState()
{
return_state;
}

publicStringgetStreet1()
{
return_street1;
}

publicStringgetStreet2()
{
return_street2;
}

publicStringgetZip()
{
return_zip;
}

publicvoidsetCity(Stringcity)
{
_city=city;
}

publicvoidsetEmail(Stringemail)
{
_email=email;
}

publicvoidsetFirstName(StringfirstName)
{
_firstName=firstName;
}

publicvoidsetHonorific(Honorifichonorific)
{
_honorific=honorific;
}

publicvoidsetLastName(StringlastName)
{
_lastName=lastName;
}

publicvoidsetPhone(Stringphone)
{
_phone=phone;
}

publicvoidsetState(Stringstate)
{
_state=state;

27/51
}

publicvoidsetStreet1(Stringstreet1)
{
_street1=street1;
}

publicvoidsetStreet2(Stringstreet2)
{
_street2=street2;
}

publicvoidsetZip(Stringzip)
{
_zip=zip;
}
}

It'sjustacollectionofgetterandsettermethods.Wealsoneedtodefinethe
enumtype,Honorific:

src/main/java/org/apache/tapestry/tutorial/data/Honorific.java:
packageorg.apache.tapestry.tutorial.data;

publicenumHonorific
{
MR,MRS,MISS,DR
}

AddressPages

We'reprobablygoingtocreateafewpagesrelatedtoaddresses:pagesfor
creatingthem,foredittingthem,forsearchingandlistingthem.We'llcreatea
subfolder,address,toholdthem.Let'sgetstartedonthefirstofthesepages,
"address/Create"(that'stherealname,includingtheslashwe'llseeina
minutehowthatmapstoclassesandtemplates).

First,we'llupdatetheStart.tmltemplate,tocreatealinkforcreatinganewpage:

src/main/webapp/Start.tml:
<h1>AddressBook</h1>

<ul>
<li><t:pagelinkpage="address/create">Createnewaddress</t:pagelink></li>

</ul>

Nowweneedthepage,let'sstartwithanemptyshell,justtotestournavigation.

src/main/webapp/address/CreateAddress.tml:
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>CreateNewAddress</title>
</head>
<body>

<h1>CreateNewAddress</h1>

28/51
<em>comingsoon...</em>

</body>
</html>

Andthecorrespondingclass:

src/main/java/org/apache/tapestry/tutorial1/pages/address/CreateAddress.java:
packageorg.apache.tapestry.tutorial.pages.address;

publicclassCreateAddress
{

So...whyistheclassnamed"CreateAddress"andnotsimply"Create"?Actually,
wecouldhavenamedit"Create",andtheapplicationwouldstillwork,butthe
longerclassnameisequallyvalid.Tapestrynoticedtheredundancyintheclass
name:org.apache.tapestry.tutorial1.pages.address.CreateAddressandjust
strippeditout.

Eventually,yourapplicationwillprobablyhavemoreentities:perhapsyou'llhave
a"user/Create"pageanda"payment/Create"pageandan"account/Create"
page.Now,youcouldhaveabunchofdifferentclassesnamedCreatespread
acrossanumberofdifferentpackages.That'slegalJava,butitisn'tideal.You
mayfindyourselfaccidentallyeditingtheJavacodeforcreatinganAccountwhen
yourreallywanttobeedittingthecodeforcreatingaPayment.

Tapestryisencouragingyoutouseamoredescriptivename:CreateAddressnot
justCreate,butitisn'tmakingyoupaythecost(intermsoflonger,uglierURLs).
TheURLtoaccessthepagewillstillbe
http://localhost:8080/tutorial1/address/create.

UsingtheBeanEditFormcomponent

Timetostartputtingtogetherthelogicforthisform.Infact,let'suseamagictrick
...theBeanEditFormcomponent.Thiscomponentcananalyzeaclassandcreate
aneditorUIforitallinonego.Let'sgiveitatry.

AddthefollowingtotheCreateAddresstemplate(replacingthe"comingsoon..."
message):

<t:beaneditformobject="address"/>

AndmatchthatupwithapropertyintheCreateAddressclass:

privateAddress_address;

publicAddressgetAddress()
{
return_address;

29/51
}

publicvoidsetAddress(Addressaddress)
{
_address=address;
}

Whenyourefreshthepage,you'llseethefollowing:

Tapestry'sdonequiteabitofworkhere.Itscreatedaformthatincludesafieldfor
eachproperty.Further,itsseenthatthehonorificpropertyisanenumeratedtype,
andpresentedthatasadropdownlist.

Inaddition,Tapestryhasconvertedthepropertynames("city","email",
"firstName")touserpresentablelabels("City","Email","FirstName").Infact,
theseare<label>elements,soclickingalabelwillmovethecursorintothe
correspondingfield.

Thisisanawesomestart;it'sapresentableinterface,quiteniceinfactforafew
minute'swork.Butit'sfarfromperfect;let'sgetstartedwithsomecustomizations.

Changingfieldorder

Itlookslikethefieldsarebeingdisplayedinalphabeticalorder,("city"first,"zip"
last).That'snotquitethereality,however:IfyoucheckthelistingfortheAddress
class,you'llseethatthegetterandsettermethodsareinalphabeticalorder(care
ofEclipse,whichgeneratedallthosemethodsfromthefields).

30/51
TheBeanEditFormworksintheorderinwhichthegettermethodsaredefinedin
theclass.Let'sreorderthemintoamorereasonableorder:

honorific
firstName
lastName
street1
street2
city
state
zip
email
phone

(Thisisalsotheorderofinwhichthefieldsaredefined.)

BecauseAddressisnotacomponentclass,itisnecessarytorestartJettytosee
theeffectsofthesechanges.

OnceJettyisrestarted,hitthebrowser'srefreshbuttontoseethefieldsinthe
correctorder:

Customizinglabels

Tapestrymakesitprettyeasytocustomizethelabelsusedonthefields.It'sjusta
matterofcreatingamessagecatalogforthepage.

InTapestry,everypageandcomponentmayhaveitsownmessagecatalog.This
isastandardJavapropertiesfile,anditisnamedthesameasthepageor
componentclass,witha".properties"extension.Amessagecatalogconsistsofa

31/51
seriesoflines,eachlineisamessagekeyandamessagevalueseperatedwith
anequalssign.

Allittakesistocreateamessageentrywithaparticularname:thenameofthe
propertysuffixedwith"label".Aselsewhere,Tapestryisforgivingofcase.

src/main/resources/org/apache/tapestry/tutorial/pages/address/CreateAddress.properties:
street1label=Street1
street2label=Street2
emaillabel=EMail
ziplabel=ZipCode
phonelabel=PhoneNumber

Sincethisisanewfile(andnotachangetoanexistingfile),youwillhavetorestart
JettytoforceTapestrytopickupthechange.

However,thingsaregettingalittlecrowdedintheform.Thattooiseasytofix:we
canjustprovideourownoverridingCascadingStyleSheet(CSS)rules.Tapestry
hasautomaticallyinjectedabuiltinCSSstylesheettoprovidethefontsand
colorsinthepage,wejustneedtotweakitabit.Add

thefollowingtoCreateAddress.html:

<style>
DIV.tbeaneditorLABEL{
width:200px;
}
</style>

32/51
The"tbeaneditor"CSSclassreferstoa<div>elementaroundthe<label>
element.Tapestry'sstyleclassesareallprefixedwith"t"(for"Tapestry")sothat
theydon'tconflictwithanythingyourownwebdesignersmaydecidetouse.In
anycase,thisCSSruleforcesthelabeltobe200pixelswide(ratherthanthe
default,whichis10%oftheoverallpagewidth).

Wecanalsocustomizetheoptionsinthedropdownlist.Allwehavetodoisadd
somemoreentriestothemessagecatalogmatchingtheenumnamestothe
desiredlabels.UpdateCreateAddress.propertiesandadd:

MR=Mr.
MRS=Mrs.
DR=Dr.

Noticethatwedon'thavetoincludeanoptionforMISS,becausethatis
convertedto"Miss"anyway.Youmightjustwanttoincludeitforconsistencie's
sake...thepointis,eachoptionlabelissearchedforseperately.

Lastly,thedefaultlabelonthesubmitbuttonis"Create/Update"(BeanEditForm
doesn'tknowhowitisbeingused).Let'schangethatto"CreateAddress".

ThatbuttonisacomponentwithintheBeanEditFormcomponent.It'snota
property,sowecan'tjustputamessageintothemessagecatalog,thewaywe
canwiththefields.Fortunately,theBeanEditFormcomponentincludesa
parameterexpresslyforrelabellingthebutton.SimplychangetheCreateAddress
componenttemplate:

<t:beaneditformsubmitlabel="CreateAddress"object="address"/>

33/51
Thedefaultforthesubmitlabelparameteris"Create/Update",butherewe're
overridingthatdefaulttoaspecificvalue.

Thefinalresultshowsthereformattingandrelabeling:

Beforecontinuingontovalidation,asidenoteaboutmessagecatalogs.Message
catalogsarenotjustforrelabelingfieldsandoptions;we'llseeinlaterchapters
howmessagecatalogsareusedinthecontextoflocalizationand
internationalization.

Insteadofputtingthelabelforthesubmitbuttondirectlyinsidethetemplate,
we'regoingtoprovideareferencetothelabel;theactuallabelwillgointhe
messagecatalog.

InTapestry,whenbindingaparameter,thevalueyouprovidemayincludea
prefix.TheprefixguidesTapestryinhowtointerprettherestofthetheparameter
value...isitthenameofaproperty?Theidofacomponent?Amessagekey?
Mostfieldshaveadefaultprefix,usually"prop:"thatisusedwhenyoufailto
provideone(thishelpstomakethetemplatesasterseaspossible).

Herewewanttoreferenceamessagefromthecatalog,soweusethe
"message:"prefix:

<t:beaneditformsubmitlabel="message:submitlabel"object="address"/>

Andthendefinethesubmitlabelkeyinthemessagecatalog:

submitlabel=CreateAddress

34/51
Atthenendoftheday,theexactsameHTMLissenttotheclient,regardlessof
whetheryouincludethelabeltextdirectlyinthetemplate,orindirectlyinthe
messagecatalog.Inthelongterm,thelatterapproachwillworkbetterifyoulater
chosetointernationalizeyourapplication.

AddingValidation

BeforeweworryaboutstoringtheAddressobject,weshouldmakesurethatthe
userprovidesreasonablevalues.Forexample,severalofthefieldsshouldbe
required,andphonenumbersandemailaddresshavespecificformats.

TheBeanEditFormchecksforaTapestryspecificannotation,@Validate,onthe
getterorsettermethodofeachproperty.

UpdatethegettermethodsforthelastName,firstName,street1,city,stateandzip
fields,addinga@Validateannotationtoeach:

@Validate("required")
publicStringgetFirstName()
{
return_firstName;
}

Whatisthatstring,"required"?That'showyouspecifythedesiredvalidation.Itis
aseriesofnamesthatidentifywhattypeofvalidationisdesired.Anumberof
validatorsarebuiltin,suchas"required","minLength"and"maxLength".As
elsewhere,Tapestryiscaseinsensitive.

Youcanapplymultiplevalidations,byseperatingthevalidatornameswith
commas.Somevalidatorscanbeconfigured(withanequalssign).Thusyou
mightsay"required,minLength=5"forafieldthatmustbespecified,andmustbe
atleastfivecharacterslong.

Restarttheapplication,andrefreshyourbrowser,thenhitthesubmitbutton.

35/51
Whatthispicturedoesn'tshowisthatthevalidationoccurredentirelyontheclient
side,usingJavaScript.Lookwhat'shappened:asummaryofalltheerrorsinthe
formhasscrolleddownatthetopoftheform,andeachforminerrorhasbeen
highlighted(it'sabitsubtle)andmarkedwithared"X".Further,thelabelforeach
ofthefieldshasalsobeenhighlightedinred,toevenmoreclearlyidentifywhat's
inerror.Thecursorhasalsobeenmovedtothefirstfieldthat'sinerror.

Again,allofthisvalidationoccuredentirelyontheclientside,norequestwas
senttotheserver.However,oncealltheerrorsarecorrected,andtheformdoes
submit,allvalidationsareperformedontheserversideaswell(justincasethe
clienthasJavaScriptdisabled).

So...howaboutsomemoreinterestingvalidationthanjust"requiredornot".
Tapestryhasbuiltinsupportforvalidatingbasedonfieldlengthandseveral
variationsoffieldvalue,includingregularexpressons.Zipcodesareprettyeasy
toexpressasaregularexpression.

@Validate("required,regexp=\\d{5}(\\d{4})?")
publicStringgetZip()
{
return_zip;
}

Let'sgiveitatry;restarttheapplicationandenteran"abc"forthezipcode.

36/51
That'stherightbehavior,butit'sthewrongmessage.Fortunately,it'seasyto
customizevalidationmessages.Allweneedtoknowisthenameoftheproperty
("zip")andthenameofthevalidator("regexp").Wecanthenputanentryintothe
CreateAddressmessagecatalog:

zipregexpmessage=ZipCodesarefiveorninedigits.Example:02134or901251655.

Refreshthepageandsubmitagain:

37/51
Thistrickisn'tlimitedtojusttheregexpvalidator,itworksequallywellwithany
validator.

Let'sgoonestepfurther.Turnsout,wecanmovetheregexppatterntothe
messagecatalogaswell.Ifyouonlyprovidethenameofthevalidatorinthe
@Validatorannotation,Tapestrywillsearchthecontainingpage'smessage
catalogoftheconstraintvalue,aswellasthevalidationmessage.Theconstraint
valuefortheregexpvalidatoristheregularexpressiontomatchagainst.

@Validate("required,regexp")
publicStringgetZip()
{
return_zip;
}

Now,justputthevalueintotheCreateAddressmessagecatalog:

zipregexp=\\d{5}(\\d{4})?
zipregexpmessage=ZipCodesarefiveorninedigits.Example:02134or901251655.

Afterarestartyou'llseethe...thesamebehavior.Butwhenwestartcreating
morecomplicatedregularexpressions,it'llbemuch,muchnicertoputthemin
themessagecatalogratherthaninsidetheannotationvalue.Andinsidethe
messagecatalog,youcanchangeandtweaktheregularexpressionswithout
havingtorestarttheapplicationeachtime.

Wecouldgoabitfurtherhere,addingmoreregularexpressionvalidationfor
phonenumbersandemailaddresses.We'realsofarfromdoneintermsof
furthercustomizationsoftheBeanEditFormcomponent.

Bynowyouarelikelycuriousaboutwhathappensaftertheformsubmits
succesfully(withoutvalidationerrors),sothat'swhatwe'llfocusonnext.

38/51
Module6
TextField&RadioButton
I
CreatePersonal.java
packageorg.apache.tapestry.tutorial.pages.personal;

importorg.apache.tapestry.annotations.Persist;
importorg.apache.tapestry.annotations.SetupRender;
importorg.apache.tapestry.tutorial.data.Address;

publicclassCreatePersonal{

@Persist
privateAddress_address;

@Persist
privateStringmessage;

publicStringgetMessage(){
returnmessage;
}

publicvoidsetMessage(Stringmessage){
this.message=message;
}

publicAddressgetAddress()
{
return_address;
}

publicvoidsetAddress(Addressaddress)
{
_address=address;
}

@Persist
privateString_position;

publicStringgetPosition(){
return_position;
}

publicvoidsetPosition(String_position){
this._position=_position;
}

@SetupRender
voidinitializeValue(){

if(getAddress()==null)
setAddress(newAddress());
}

ObjectonSuccess()
{
setMessage("SUCCESSUPDATE");
returnnull;
}
}

CreatePersonal.html
<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>
<title>CreatePersonal</title>

39/51
</head>
<t:Form>

<h1>CreatePersonal</h1>

<tableborder="0">
<tr>
<tdcolSpan="4"><t:errors/></td>
</tr>
<tr>
<td>FirstName</td>
<td>:</td>
<td><INPUTt:type="textField"value="address.firstName"/></td>
</tr>
<tr>
<td>LastName</td>
<td>:</td>
<td><INPUTt:type="textField"value="address.lastName"/></td>
</tr>
<tr>
<tdvAlign="top">Position</td>
<tdvAlign="top">:</td>
<td>
<t:radiogroupt:id="position">

<t:radiot:id="radio1"value="literal:MANAGER"label="Manager"/>
<t:labelfor="radio1"/>
<br/>
<t:radiot:id="radio2"value="literal:PROGRAMMER"label="Programmer"/>
<t:labelfor="radio2"/>

</t:radiogroup>
</td>
</tr>
</table>
<p>
<inputtype="submit"value="Update"/>
</p>
<hr/>
</t:Form>
<t:iftest="address.firstName">
FirstName:${address.firstName}
</t:if>

<t:iftest="position">
<br/>
Position:${position}
</t:if>
<hr/>
<p>${message}</p>
</html>

40/51
41/51
Module7
SelectWithObjects
Creatinga<SELECT>isabitcomplicatedandthereisonlysupportforstrings
andenumscurrently.(T5.0.5)

TapestrySelectuses:

SelectModeltogetgroupsandoptions,anditisresponsiblefordisplayedtext
(<optionvalue=..>TEXT</option>)

ValueEncodertogeneratevalueparameterfor<option>tagandrestorethe
selectedvaluebackafterformissubmitted

Enumsandstringsareok,butmanypeoplewillwanttouseObjects(Beans,
POJOsfromdatabaseviaORM).Thishasproventobeadificulttask,especialy
sinceweareallnewtoT5.

HereisanexampleofaSelectionModelthatsimplifiesusingSelectcomponent
withobjects.Itrequires5parameters

List<T>thelistofobjectsthatcanbeselected

Class<T>Superclassofallobjectsinthelist(thisisbecauseofGenerics
deletionatruntime)soanadaptercanbeproduced(evenbytecodegenerated
onemightbeimplementedinTapestryioclateron)

nameoftheidentifierpropertyapropertythatidentifiestheobject(usualyidif
objectisfromdatabase),itwillbeusedasvalueattributeforthe<option>

nameofthenamepropertyifnameiscomposedoffewproperties,addan
transientpropertytoyourObject

PropertyAccessthisisanTapestryiocservicethattheselectionmodelusesto
accesspropertiesfromtheobjectandyoumustsupplyit.Youcangetinjectitinto
yourpageeasily

@InjectprivatePropertyAccess_access;

AnothercatchisthatValueencoderneedstheoriginallisttorecreatetheobject
laterafterformissubmitted.SoweimplementValueEncoderinterfacedirectlyin
ourSelectModelimplementation,andsupplythemodelbothasmodeland
encoderparameterofSelectcomponent.

<t:selectmodel="myModel"encoder="myModel"value="someBean"/>

42/51
Here'scodefortheSelecModelimplementation:GenericSelectModel

importjava.util.ArrayList;
importjava.util.List;

importorg.apache.tapestry.OptionGroupModel;
importorg.apache.tapestry.OptionModel;
importorg.apache.tapestry.ValueEncoder;
importorg.apache.tapestry.internal.OptionModelImpl;
importorg.apache.tapestry.ioc.services.PropertyAccess;
importorg.apache.tapestry.ioc.services.PropertyAdapter;
importorg.apache.tapestry.util.AbstractSelectModel;

/**GenericselectionmodelforalistofObjects.
*use:
*<pre>@InjectprivatePropertyAccess_access;</pre>
*inyourpagetogethe{@linkPropertyAccess}service.<br>
*!Notice:youmustsetthecreatedinstancebothasmodelandencoderparameterfor
the
*{@linkSelect}component.*/
publicclassGenericSelectModel<T>extendsAbstractSelectModelimplements
ValueEncoder<T>{

privatePropertyAdapterlabelFieldAdapter;
privatePropertyAdapteridFieldAdapter;
privateList<T>list;

publicGenericSelectModel(List<T>list,Class<T>clazz,StringlabelField,String
idField,PropertyAccessaccess){
this.list=list;
if(idField!=null)
this.idFieldAdapter=access.getAdapter(clazz).getPropertyAdapter(idField);
if(labelField!=null)
this.labelFieldAdapter=
access.getAdapter(clazz).getPropertyAdapter(labelField);
}

publicList<OptionGroupModel>getOptionGroups(){
returnnull;
}

publicList<OptionModel>getOptions(){
List<OptionModel>optionModelList=newArrayList<OptionModel>();
if(labelFieldAdapter==null){
for(Tobj:list){
optionModelList.add(newOptionModelImpl(nvl(obj),false,obj,new
String[0]));
}
}else{
for(Tobj:list){
optionModelList.add(newOptionModelImpl(nvl(labelFieldAdapter.get(obj)),
false,obj,newString[0]));
}
}
returnoptionModelList;
}

//ValueEncoderfunctions
publicStringtoClient(Tobj){
if(idFieldAdapter==null){
returnobj+"";
}else{
returnidFieldAdapter.get(obj)+"";
}
}

publicTtoValue(Stringstring){
if(idFieldAdapter==null){
for(Tobj:list){
if(nvl(obj).equals(string))returnobj;
}
}else{
for(Tobj:list){
if(nvl(idFieldAdapter.get(obj)).equals(string))returnobj;
}
}
returnnull;
}

43/51
privateStringnvl(Objecto){
if(o==null)
return"";
else
returno.toString();
}
}

here'sanexampleBean

packageorg.apache.tapestry.tutorial.data;

publicclassSomeBean{
Integerid;
Stringname;

publicSomeBean(Integerid,Stringname){
this.id=id;
this.name=name;
}
publicIntegergetId(){
returnid;
}
publicvoidsetId(Integerid){
this.id=id;
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
}

Examplepageclass

publicclassSelectTest{
@Persist
privateSomeBean_someBean;
@Inject
privatePropertyAccess_access;

privateGenericSelectModel<SomeBean>_beans;

publicSelectTest(){
ArrayList<SomeBean>list=newArrayList<SomeBean>();
list.add(newSomeBean(1,"Mirko"));
list.add(newSomeBean(2,"Slavko"));
list.add(newSomeBean(3,"Jozo"));
_beans=new
GenericSelectModel<SomeBean>(list,SomeBean.class,"name","id",_access);
}

publicSomeBeangetSomeBean(){
return_someBean;
}

publicvoidsetSomeBean(SomeBean_someBean){
this._someBean=_someBean;
}

publicGenericSelectModel<SomeBean>getBeans(){
return_beans;
}
}

thetestpage:

<htmlxmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
<head>

44/51
<metahttpequiv="contenttype"content="text/html;charset=utf8"/>
<title>Selecttest</title>
</head>
<body>
<h1>selectwithobject</h1>
<formt:type="Form">
<t:selectmodel="beans"encoder="beans"value="someBean"/>
<t:submit/>
</form>
value:${someBean.id}
</t:if>
</body>

</html>

45/51
Module8
AcegiOnTapestry
First,youneedtoaddthecorrectdependenciestoyourpom.xmlfile.This
exampleassumesversion1.0,however,checkthewebsite,astheremightbe
newversionsavailable.

<dependency>
<groupId>nu.localhost.tapestry</groupId>
<artifactId>tapestry5acegi</artifactId>
<version>1.0</version>
</dependency>

Wealsoneedtoaddtheremoterepository.

<repository>
<id>localhost.nu</id>
<url>http://www.localhost.nu/java/mvn</url>
</repository>

Allconfigurationsymbolsprovidedbytapestry5acegicanbeoverridenusingthe
usualTapestrymethods.Inthisexamplewechangethedefaultpassword
encoderandtheurlcalledwhenwefailedtologin.

publicstaticvoidcontributeApplicationDefaults(MappedConfiguration<String,
String>configuration)
{
configuration.add("acegi.failure.url","/loginpage/failed");
configuration.add("acegi.password.encoder",
"org.acegisecurity.providers.encoding.Md5PasswordEncoder");
}

Wealsoneedsomesortofauthenticationprovider,mostlikelyyouwillbeusing
daoAuthenticationManagerwhichcanbeusedlikethis.

publicstaticvoidbind(ServiceBinderbinder){
binder.bind(UserDetailsService.class,UserDetailsServiceImpl.class);
}

publicstaticUserDetailsServicebuildUserDetailsService(Sessionsession){
returnnewUserDetailsServiceImpl(session);
}

publicstaticvoidcontributeProviderManager(
OrderedConfiguration<AuthenticationProvider>configuration,
@InjectService("DaoAuthenticationProvider")AuthenticationProvider
daoAuthenticationProvider){
configuration.add("daoAuthenticationProvider",daoAuthenticationProvider);
}

46/51
LoginPage

AfterloginSuccessful

47/51
Module9
Tapestry/SpringIntegration
InthismodulewewillusealibrarythatprovidesintegrationbetweenTapestry
andSpring,allowingbeansdefinedbySpringtobeinjectedintoTapestryIoC
services,andintoTapestrycomponents.

ThelibraryiscompiledandtestedagainstSpring1.2.8.However,Spring2.0is
fullybackwardscompatibletoSpring1.2.8.

ThislibraryusestheMavenscope"provided"forthedependenciesonSpring.
ThismeansthatinyourownPOM,youwillneedtospecifyyourowndependency
toSpring,includingthecorrectversion.Example:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>springweb</artifactId>
<version>1.2.8</version>
</dependency>

WiththedefaultMavenscope,theSpringJARsanddependencieswillbe
packagedintoyourapplication'sWARfile.

Usage

TheintegrationisdesignedtobeaverythinlayerontopofSpring'snormal
configurationforawebapplication.

DetailedinstructionsareavailableintheSpring1.2.xdocumentation.

web.xmlchanges

Theshortformisthatyoumustmaketwosmallchangestoyourapplication's
web.xml.

First,aspecialfilterisusedinreplaceofthestandardTapestryFilter:

<filter>
<filtername>app</filtername>
<!SpecialfilterthataddsinaT5IoCmodulederivedfromthe
SpringWebApplicationContext.>
<filter
class>org.apache.tapestry.spring.TapestrySpringFilter</filterclass>
</filter>

Secondly,youmustaddthenormalSpringconfiguration,consistingofa
<listener>element,and(optionally)a<contextparam>identifyingwhichSpring
beanconfigurationfile(s)toload:

48/51
<contextparam>
<paramname>contextConfigLocation</paramname>
<paramvalue>/WEBINF/daoContext.xml/WEB
INF/applicationContext.xml</paramvalue>
</contextparam>

<listener>
<listener
class>org.springframework.web.context.ContextLoaderListener</listener
class>
</listener>

The<contextparam>liststheSpringbeanconfigurationfile.Itisoptionaland
defaultstojust/WEBINF/applicationContext.xmlifomitted.

TheContextLoaderListenerisresponsibleforreadingthebeanconfigurationfile
(orfiles)andstoringtheresultintheapplicationcontext,wheretheTapestry
Springintegrationcodecanmakeuseofit.

Injectingbeans

Insideyourcomponentclasses,youmayusetheInjectannotation.Typically,just
thefieldtypeissufficienttoidentifytheSpringbeantoinject:

@Inject
privateUserDAO_userDAO;

Ifyouhavemultiplebeansthatimplementthesameinterface(forinstance,ifyou
havewrappedyourbeanusingatransactioninterceptor),youmustdisambiguate.
TheeasiestwaytoaccomplishthisistoaddaServiceannotationtoidentifythe
nameoftheSpringbean:

@Inject
@Service("UserDAO")
privateUserDAO_userDAO;

InjectionofSpringbeansviaservicebuildermethodsorautobuildingoccursjust
thesame:theSpringbeansmasqueradeasTapestryIoCservicesandalliswell.

CaseInsensitivity
SpringbeansnamesaretreatedexactlyasTapestryIoCserviceids.Since
serviceidsarecaseinsensitive,accesstoSpringbeansbybeannamewillalso
becaseinsensitive.

WebApplicationContextService
TheSpringWebApplicationContextisalsoaddedasaservice,inadditiontoany
servicesdefinedwithinthecontext.

49/51
Limitations
Thenamesofbeansareobtainedatapplicationstartup.Ifnewbeansare
programatticallyaddedtotheSpringapplicationcontextatruntime,thesewillnot
bevisibleforinjection.

Onlythebeannameisused,notanyofthebean'saliases.

Nocheckismadefornameclashesthatwouldoccurwhentwobeanshave
namesthatdifferonlyintermsofcapitalization.Ifyou'regoingtogoaround
namingbeans"userDAO"and"UserDao",you'rejustaskingfortrouble.

Nonsingletonbeansarenothandledproperly.Tapestrywillrequestthebeans
fromtheapplicationcontextinamannerunsuitablefortheirlifecycle.Forthe
moment,youshouldconsiderthenonsingletonbeanstobenotinjectable.
Instead,injecttheWebApplicationContextserviceandobtainthenonsingleton
beansasneeded.

50/51
References

Thefollowingreferencesprovideadditionalinformationonthe
topicsdescribedinthistraningmodule:
HowardLewisShip,TapestryTutorial:Introduction
[http://tapestry.apache.org/tapestry5/tutorial1/]
TapestryWiki,Tapestry5HowTos
[http://wiki.apache.org/tapestry/Tapestry5HowTos

51/51

You might also like