For developers experienced at servlet Tapestry, this one issue takes quite some getting used to.
In servlet Tapestry, a request comes in (such as a form submission). As part of processing the request, pages will be obtained from the page pool and will have persistent state rolled back. Other page and component properties will contain transient state; information that will be discarded at the end of the request ... but that's ok, because a response page will be rendered before that information is lost.
In portlet Tapestry, much is the same, except for transient state . The Portlet API splits the scenario into two completely distrinct parts: the action request and the render request. These are two completely seperate requests, and all transient state will be lost between them. Further, the render request may occur many times. This is because a single portlet within a page may (depending on the Portlet container and configuration of the portlet) have to re-render itself due to an action request for some entirely different portlet.
The solution is to define more properties as persistent . This is done using the <property> element of the page and component specifications, such as:
<property name="itemId" persist="session"/>
Commonly, the interface is used to obtain a database object, bootstrapping from an id stored as a persistent property:
public abstract class MyPage extends BasePage implements PageBeginRenderListener { /** Persistent */ public abstract long getItemId(); public abstract void setItemId(long itemId); /** Transient */ public abstract Item getItem(); public abstract void setItem(Item item); /** Injected */ public abstract ItemSource getItemSource(); public void pageBeginRender(PageEvent event) { long itemId = getItemId(); Item item = getItemSource().findItemById(itemId); setItem(item); } }
One of the most common problems related to loss of transient state concerns user input validation . By default , validation data is stored transiently. The end result is that when a validated form is submitted with errors and re-rendered, the error messages and field decorations do not appear!
A good technique for handling this properly is to make the validation delegate an application state object that persists in the session, and then inject that ASO into each page that contains a form.
Start by defining an ASO for the validation delegate. The goes into your application's hivemodule.xml module descriptor:
<contribution configuration-id="hivemind.state.ApplicationObjects"> <state-object name="validation-delegate" scope="session"> <create-instance class="org.apache.tapestry.valid.ValidationDelegate"/> </state-object> </contribution>
Next, in each page that needs the validation delegate, inject it as a property. This is done in the page's specification:
<inject name="delegate" type="state" object="validation-delegate"/>
When using a component, override the default validation delegate:
<component id="form" type="Form"> <binding name="delegate" value="delegate"/> <binding name="listener" value="doSubmit"/> </component>
The delegate parameter's default binding prefix is "ognl", so
value="delegate"
refers to the delegate property of the page, a read-only property created by the
<inject> element.
And that's it ... the validation delegate will be created when first needed. A single instance will be shared by many pages. It will be necessary to invoke the clear() method on the validation delegate when switching pages (otherwise, you could see decorations one one page that are really caused by form input errors on some other page). This is an extra step to be done inside your listener methods.
Beginning with Tapestry 4.1 > portlets must also now manage including the global Tapestry and Dojo javascript packages that the framework depends on - something which is normally done for you by using the Shell component.
Luckily this should be a fairly trivial task if you use the ScriptIncludes component which was created specifically for these scenerios. Simply dump it into your portlet pages somewhere and it will properly include/configure the necessary javascript packages for you.
A fundamental part of developing Portlets (with our without Tapestry) is that each Portlet within a Portal Page produces just a portion of the HTML. This means you will not, and should not, use the or components.
The latter seems problematic ... no component normally means no client-side JavaScript! Fortunately, your components can still create JavaScript, using Tapestry script templates, and the JavaScript will still be collected as if there was a component. You may also see the namespace, provided by the portlet container, integrated into the names of client-side variables and methods ... this prevents any name collisions when multiple Tapestry portlets exist on the same page.
In very rare circumstances, you may need to directly access the PortletRequest object. For example, you may make part of your page conditional based on the current window mode.
If you are just interseted in query parameters, you can access those via the object.
Most other standard information can be injected as the infrastructure:request object. This object is API neutral (neither servlet nor portlet, but a kind of least-common-denominator abstraction). It can easily be injected into any page or component using the specification's <inject> element:
<inject property="request" object="infrastructure:request"/>
The type of object injected will be . There's also an infrastructure:response object that implements the interface.
Note:This may look odd; you are injecting something that looks like an application global. Don't worry ... what's actually injected is a proxy object that implements the interface, but ultimately delegates all of its behavior to a per-request (and therefore, per-thread) object. It just works.
Properties such as portletMode or windowState are not part of , you'll want to inject a portlet-specific object instead:
Object | Interface |
---|---|
service:tapestry.portlet.ActionResponse | javax.portlet.ActionResponse |
service:tapestry.portlet.PortletRequest | javax.portlet.PortletRequest |
service:tapestry.portlet.RenderResponse | javax.portlet.RenderResponse |
Care should be taken to only invoke methods on the ActionResponse and RenderResponse objects when in the correct phase.
Part of the <portlet> definition within the portlet.xml deployment descriptor is a definition of user attributes. User attributes are configuration data stored for each user (this is part of the infrastructure provided by the Portal: user authentication and attributes). User attribute names are strings, usually in the form of a dotted sequence of terms (such as "user.name.given"). User attribute values are simple strings.
User attributes are stored persistently in a database; values will be available in later sessions, not just later requests.
A special binding prefix, "user:", is used to access user attributes.
So, a particular portlet may define an attribute:
<user-attribute> <description>User Given Name</description> <name>user.name.given</name> </user-attribute>
Inside a page template, this value can be read and displayed:
<span jwcid="@Insert" value="user:user.name.given">User Name</span>
These values can just as easily be used with form element components, such as , to read and update attributes:
<input jwcid="userName@ValidField" value="user:user.name.given" displayName="User Name" validator="string,required"/>