Tapestry 4.0 Notes
Sample page file
<?xml version="1.0"?> <!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd"> <page-specification class="com.foo.SomePage"> <description>Page Description</description> <parameter name="title" property="titleParameter"/> <property name="error"/> <bean name="delegate" class="com.foo.ValidationDelegate"/> <bean name="postalCodeValidator" class="org.apache.tapestry.form.validator.Pattern"> <set name="pattern">'[A-CEGHJ-NPR-TVWY]\\d[A-Z]\\s\\d[A-Z]\\d'</set> <set name="message" value="message:error.postal.code"/> </bean> <asset name="submit" path="context:/images/button-submit.gif"/> <asset name="cancel" path="context:/images/button-cancel.gif"/> </page-specification >
Sample jwc file
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE component-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd"> <component-specification class="com.foo.SomePageComponent" allow-body="yes" allow-informal-parameters="yes"> <description>Component Description</description> <parameter name="title" property="titleParameter"/> <property name="error"/> <bean name="delegate" class="com.foo.ValidationDelegate"/> <bean name="postalCodeValidator" class="org.apache.tapestry.form.validator.Pattern"> <set name="pattern">'[A-CEGHJ-NPR-TVWY]\\d[A-Z]\\s\\d[A-Z]\\d'</set> <set name="message" value="message:error.postal.code"/> </bean> <asset name="submit" path="context:/images/button-submit.gif"/> <asset name="cancel" path="context:/images/button-cancel.gif"/> </component-specification>
Component java class
- If there is a parameter that needs to be accessed by a java class in a component, leave the parameter out of the jwc file and include this in the java class:
... @Parameter(required = true) public abstract String getSomeVariable(); ...
Goto a new page
cycle.activate( "PageName" );
getRequestCycle().activate( "PageName" );
| Warning A PageRedirectException cannot be used inside a pageBeginRender method. |
throw new PageRedirectException( getRequestCycle().getPage("PageName" ) );
@InjectObject("engine-service:page") public abstract IEngineService getPageService(); // ... public ILink submitForm() { // ... if( isInError() ) { delegate.record( getInErrorMessage(), ValidationConstraint.CONSISTENCY ); return null; } if( isFormError() ) { delegate.record( (IFormComponent) getPage().getComponent( "userId" ), getFormErrorMessage() ); } // Returning a link from a listener will force a Redirect-after-Post return getPageService().getLink( false, "PageName" ); }
| Warning If you have a listener attached to the form submit and have listeners on the buttons, a redirect in the button listeners will prevent the form submit listener from being called. |
Set a variable on a new page and goto it
- Get the page directly and programmatically set the variable:
AgentEdit page = (AgentEdit) getRequestCycle().getPage( "AgentEdit" );
page.setAgentId( agentId );
getRequestCycle().activate( page );
- Use an external link, allows the parameter to be part of url.
- Be careful, only use primatives and not too many of them.
- Listener should implement IExternalPage and looks like:
// ... @InjectObject("engine-service:external") public abstract IEngineService getExternalService(); // ... Object[] parameters = new Object[] { "SomeValue" }; return getExternalService().getLink( false, new ExternalServiceParameter( "NextPage", parameters ) ); // ...
- Redirected page looks like:
// ... public void activateExternalPage( Object[] arg0, IRequestCycle cycle ) { String someValue= (String) arg0[ 0 ]; // ... } // ...
Using annotations to get properties
@Message("text.someProperty") public abstract String getSomePropertyMessage();
Validation: Simple required field
<bean name="delegate" class="org.apache.tapestry.valid.ValidationDelegate" property="delegate"/> ... <component id="name" type="TextField"> <binding name="value" value="name"/> <binding name="validators" value="validators:required[%error.missing-name]"/> </component>
error.missing-name=Missing name.
<span key="label.name"/> <input jwcid="name" size="35" maxlength="35" />
| Warning If you have a listener attached to the form submit and have listeners on the buttons, a redirect in the button listeners will prevent the form submit listener from being called. |
Validation: Advanced validation
<bean name="delegate" class="org.apache.tapestry.valid.ValidationDelegate" property="delegate"/> <bean name="alphanumericValidator" class="org.apache.tapestry.form.validator.Pattern"> <set name="pattern">'[A-Z,a-z,0-9]*'</set> <set name="message" value="message:error.must-be-alphanumeric"/> </bean> ... <component id="name" type="TextField"> <binding name="displayName" value="message:error.name"/> <binding name="value" value="name"/> <binding name="validators" value="validators:required[%error.missing-name], minLength=3[%error.name-too-short], maxLength=10[%error.name-too-long], $alphanumericValidator"/> </component>
error.missing-name=Missing name. error.name-too-short=Must be 3 characters. error.name-too-long=Cannot be greater then 10 characters. error.must-be-alphanumeric=Must be alphanumeric.
<span key="label.name"/> <input jwcid="name" size="35" maxlength="35" />
Validation: pre-rendered errors
- To display an error message when a page is being displayed for the first time.
... @Override public void pageEndRender( PageEvent event ) { if( displayErrorMessage() ) { setError( "Some error message." ); getValidationDelegate().record( (IFormComponent) getPage().getComponent( "someComponentId" ), SOME_ERROR_KEY ); } ...
Listeners
There are several different ways to use listeners.
As the part of a DirectLink, using either an 'ognl:listeners.' or 'listener:' notation:
<span jwcid="@DirectLink" listener="ognl:listeners.Logout">
<span jwcid="@DirectLink" listener="listener:Logout">
As the default form listener:
<form jwcid="@Form" stateful="false" delegate="ognl:beans.delegate" listener="listener:save"> ... <input jwcid="@Submit" value="message:button.save"/> <input jwcid="@Submit" listener="listener:cancel" value="message:button.cancel"/> </form>
| If you use multiple parameters on a method, do not use an Object[]. Rather, specify them explicitly in the listener method signature. |
<span jwcid="@DirectLink" listener="listener:someListener" parameters="ognl:{ 'a', 'b', 'c' }"> click </span>
public ILink someListener( String argA, String argB, String ArgC ) { ... }
Commonly overriden methods
public abstract class SomePage extends BasePage { ... public void pageBeginRender(PageEvent event) { //Code to run when page starts. } ... }
public abstract class SomeComponent extends BaseComponent { ... public void prepareForRender( IRequestCycle cycle ) { //Code to run when component starts. } ... }
Protected Page
The page class (or it's base class) must implement PageValidateListener and the method pageValidate() setup to redirect (usually throw a PageRedirectException) to a login page if not authenticated.
public abstract class ProtectedBasePage extends BasePage implements PageValidateListener { ... public void pageValidate( PageEvent event ) { if ( !"password".equals( getPassword() ) ) { throw new PageRedirectException("Home"); } } ... }
If logic in html
Any logic in the html page must be replaced with entities. Furthermore, the ognl: is only required for the opening of the condition.
<span jwcid="@If" condition="ognl:firstBoolean && secondBoolean == null"> ... </span> <span jwcid="@Else"> ... </span>
Using ognl variables in javascript call
To mix an ognl expression in a javascript call, the expression must be part of a jwcid element and the expression itself must start with ognl.
<input jwcid="@Button" type="button" onclick="ognl:'SomeFunctionClass.someMethod(\'' + tapestryOgnlVariable+ '\')'" value="Cancel">Cancel</input>
Clearing a form using ognl variables in a javascript call
Tapestry will override any form id if there are multiple forms on a page, relying on Form0 is not safe.
Instead use ognl in the javascript to explicitly refer to the component id.
... <form jwcid="FormComponentId@Form"> ... </form> ... <a jwcid="@Any" href="javascript:void(0)" onClick="ognl:'resetForm(\'' + getComponent('FormComponentId').name + '\')'"/> ...
Date formatting
Using a property to format the display of a date.
... @Message("format.date") public abstract String getDateFormatProperty(); ... private Format dateFormat; public Format getDateFormat() { if( dateFormat == null ) dateFormat = new SimpleDateFormat( getDateFormatProperty() ); return dateFormat; } ...
... <span jwcid="@Insert" value="ognl:someDateValue" format="ognl:dateFormat" /> ...
... format.date=MM/dd/yyyy ...
Persisting data
... @Persist public abstract String getFoo(); public abstract void setFoo( String value ); ...
Redirect after post
public abstract class SubmitPage extends SomeBasePage { ... @InjectObject("engine-service:external") public abstract IEngineService getExternalService(); ... public ILink someListenerMethod() { IValidationDelegate delegate = (IValidationDelegate) getBeans().getBean( "delegate" ); //Bounce if the Required validator has errors. if( delegate.getHasErrors() ) { return null; } ... Object[] parameters = new Object[] { SUCCESS_KEY }; return getExternalService().getLink( false, new ExternalServiceParameter( "NextPage", parameters ) ); } ...
public abstract class NextPage extends SomeBasePage implements IExternalPage { ... public void activateExternalPage( Object[] parameters, IRequestCycle cycle ) { setSuccess( getUiMessage( (String) parameters[ 0 ] ) ); } ...
- Alternatively, use getPageService():
return getPageService().getLink( false, "NextPage" ); ...
Zebra stripes on a table
<span jwcid="@For" source="ognl:agents" value="ognl:agent" element="tr" index="ognl:rowIndex" class='ognl:rowIndex%2==0?"r1":"r2"'>
<property name="rowIndex" />
Using a Render Block with a If Else
<span jwcid="@If" condition="ognl:isAgentNumberInErrorFlag"> <div class="error"> <span jwcid="@RenderBlock" block="ognl:agentNumberBlock"/> </div> </span> <span jwcid="@Else"> <span jwcid="@RenderBlock" block="ognl:agentNumberBlock"/> </span> <span jwcid="agentNumberBlock@Block"> <label jwcid="@FieldLabel" field="component:agentNumber" displayName="message:label.agent-number"/> <input type="text" size="9" maxlength="9" jwcid="agentNumber@LoyaltyTextField" value="ognl:agentNumber" validators="validators:agentNumberValidator" /> </span>
Using properties
text.foo=Foo
- The raw="true" will insert the properties without translating values.
<p class="someClass"> <span jwcid="@Insert" value="message:text.foo" raw="true"/> </p>
Credit and Thanks
Michael Prescott
Dave Park