You are on page 1of 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