Div |
---|
Page Navigation
Wiki Markup | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
| |background=#eee}
{contentbylabel:title=Related Articles|showLabels=false|showSpace=false|space=@self|labels=request-processing,rendering}
{float}
| ||||||||
|
In essence, a Tapestry application is a number of related pages, working together. To some degree, each page is like an application unto itself.
...
- component event requests target a specific component on a specific page, triggering an event within that component
- render requests target a specific page, and stream the HTML markup for that page back to the client
...
This dichotomy between component event requests and render requests is new in Tapestry 5. It is in some ways based on ideas from the Portlet specification and differentiating the two types of requests alleviates a number of problems in traditional web applications related to the browser back button, or to the user hitting the refresh button in their browser.
Contents
Table of Contents | ||
---|---|---|
|
Logical Page Name Shortening
In certain cases, Tapestry will shorten the the logical name of a page. For example, the page class org.example.pages.address.CreateAddress will be given a logical name of "address/Create" (the redundant "Address" is removed as a suffix). However, this only affects how the page is referenced in URLs; the template file will still be CreateAddress.tml, whether on the classpath, or as address/CreateAddress.tml (in the web context).
...
Tapestry actually creates multiple names for the name page: "address/Create" and "address/CreateAddress" are both synonymous. You can user either in Java code that refers to a page by name, or as the page parameter of a PageLink.
Component Event Requests & Responses
Main Article: Component Events
Component event requests may take the form of hyperlinks (EventLink or ActionLink) or form submissions (Form).
...
The URL for a component event request identifies the name of the page, the nested id of the component, and the name of the event to trigger on the component (this is usually specified by the "event" parameter of EventLink, or "action" for an ActionLink). Further, a component event request may contain additional context information, which will be provided to the event handler method.
...
If the event handler method returns no value, or returns null, then the current page (the page containing the component) will render the response.
A page render link URL for the current page is created and sent to the client as a client side redirect. The client browser will automatically submit a new request to generate the page.
...
An event handler method may return a an instance of HttpError instance to send an error response to the client.
...
The ComponentResources object that is injected into your pages (and components) has methods for creating component event and page render links.
7. Stream response
The PageRenderLinkSource service can be injected to allow links to other pages to be created (though that is rarely necessary, given the other options listed above).
7. Stream response
An event handler can also An event handler can also return a StreamResponse object, which encapsulates a stream to be sent directly to the client browser. This is useful for components that want to, say, generate an image or PDF and provide it to the client.
8. URL response
A java.net.URL response is handled as a client redirect to an external URL. (In Tapestry 5.3.x and earlier this only works for non-Ajax requests.)
9. Object response
Any other type of object returned from an event handler method is an error.
Page Render Requests
Render requests are simpler in structure and behavior than component event requests. In the simplest case, the URL is simply the logical name of the page.
Pages may have an activation context. The activation context represents persistent information about the state of the page. In practical terms, the activation context is usually the id of some database-persistent object.
When a page has an activation context, the values of the context are appended to the URL path.
Not all pages have an activation context.
The activation context may be explicitly set when the render request link is created (the PageLink component has a context parameter for this purpose). When no explicit activation context is provided, the page itself is queried for its activation context.
:
Code Block |
---|
public Object onAction(){
return new StreamResponse() {
@Override
public String getContentType() {
return "application/pdf";
}
@Override
public InputStream getStream() throws IOException {
return new ByteArrayInputStream(getMyPdfByteArray());
}
@Override
public void prepareResponse(Response response) {
response.setHeader("Content-Disposition", "attachment; filename=\"" + myFileName + "\"");
}
};
} |
8. URL response
A java.net.URL response is handled as a client redirect to an external URL. (In Tapestry 5.3.x and earlier this only works for non-Ajax requests.)
9. Object response
Any other type of object returned from an event handler method is an error.
Page Render Requests
Render requests are simpler in structure and behavior than component event requests. In the simplest case, the URL is simply the logical name of the page.
Pages may have an activation context. The activation context represents persistent information about the state of the page. In practical terms, the activation context is usually the id of some database-persistent object.
When a page has an activation context, the values of the context are appended to the URL path. For example, in http://www.example.com/myapp/foo/bar
the "myapp" part is the servlet context (usually the name of your app), and the "foo/bar" part is the activation context, with "foo" being the first activation parameter and "bar" being the second.
It is common for most pages to not have any activation context.
The activation context may be explicitly set when the render request link is created (the PageLink component has a context parameter for this purpose).
When no explicit activation context is provided, the page itself is queried for its activation context. This querying takes the form of an event trigger. The event name is "passivate" (as we'll see shortly, there's a corresponding "activate"). The return value of the method is used as the context. For example:
Code Block | ||||
---|---|---|---|---|
| ||||
public class ProductDetail
{
private Product product;
. . .
long onPassivate() { return product.getId(); }
}
|
The activation context may consist of a series of values, in which case the return value of the method should be an array or a List.
Info |
---|
Note: If you are using the tapestry-hibernate integration library and your passivate context is a Hibernate entity, then you can just use the entity itself, not its id. Tapestry will automatically extract the entity's id into the URL, and convert it back for the "activate" event handler method. |
Page Activation
When a page render request arrives, the page is activated before it is rendered.
Wiki Markup |
---|
{float:right|background=#eee|padding=0 1em}
*JumpStart Demos:*
[onActivate and onPassivate|https://tapestry-jumpstart.org/jumpstart/examples/navigation/onactivateandonpassivate/3]
[Handling A Bad Context|https://tapestry-jumpstart.org/jumpstart/examples/infrastructure/handlingabadcontext/1]
{float} |
Activation serves two purposes:
- It allows the page to restore its internal state from data encoded into the URL (the activation context discussed above).
- It provides coarse approach to validating access to the page.
The later case – validation – is generally concerned with user identity and access; if you have pages that may only be accessed by certain users, you may use the page's activate event handler for verifying that access.
Page activation uses Tapestry's Component Event mechanism. See Component Events for details.
A page's activate event handler mirrors its passivate handlerThis querying takes the form of an event trigger. The event name is "passivate" (as we'll see shortly, there's a corresponding "activate"). The return value of the method is used as the context. For example:
Code Block | ||||
---|---|---|---|---|
| ||||
public class ProductDetail { private Product product; . . . void onActivate(long onPassivate(productId) { return product = productDAO.getIdgetById(productId); } } |
The activation context may consist of a series of values, in which case the return value of the method should be an array or a List.
Info |
---|
Note: If you are using the tapestry-hibernate integration library and your passivate context is a Hibernate entity, then you can just use the entity itself, not its id. Tapestry will automatically extract the entity's id into the URL, and convert it back for the "activate" event handler method. |
Page activation
When a page render request arrives, the page is activated before it is rendered.
Activation serves two purposes:
- It allows the page to restore its internal state from data encoded into the URL (the activation context discussed above).
- It provides coarse approach to validating access to the page.
The later case, validation, is generally concerned with user identity and access; if you have pages that may only be accessed by certain users, you may use the page's activate event handler responsible for verifying that access.
A page's activate event handler mirrors its passivate handler:
...
. . .
void onActivate(long productId)
{
product = productDAO.getById(productId);
}
. . .
. . .
|
Here's the relevant part: when the page renders, it is likely to include more component event request URLs (links and forms). The component event requests for those links and forms will also start by activating the page, before performing other work. This forms an unbroken chain of requests that include the same activation context.
To some degree, this same effect could be accomplished using a persistent page value, but that requires an active servlet session, and the result is not bookmarkable.
Your activate event handler, like any event handler, may also return a value, which is treated identically to a return value of a component event method. This technique is commonly used as a simple access validation mechanism.
You sometimes need to handle multiple page activation scenarios in one page class. You could create multiple activate event handler methods with different arguments (see the "Multiple Method Matches" section at Component Events for details), but if you do so, you should generally return true
from each to avoid having more than one activation event handler method from being called for each page request. However, a better approach is to create one method with an EventContext argument. Tapestry will populate the EventContext argument with all of the activation parameters, and the EventContext's get
method will retrieve and coerce each parameter to the desired type. For example:
Code Block | ||||
---|---|---|---|---|
| ||||
. . .
void onActivate(EventContext eventContext) {
String color = DEFAULT_COLOR;
if (eventContext.getCount() > 0) {
long productId = eventContext.get(Long.class, 0);
if (eventContext.getCount() > 1) {
String color = eventContext.get(String.class, 1);
}
product = productDAO.getById(productId, color);
} else {
return SelectProduct.class; // no product selected
}
}
. . .
|
Here's the relevant part: when the page renders, it is likely to include more component event request URLs (links and forms). The component event requests for those links and forms will also start by activating the page, before performing other work. This forms an unbroken chain of requests that include the same activation context.
To some degree, this same effect could be accomplished using a persistent page value, but that requires an active session, and the result is not bookmarkable.
...
Page Navigation Patterns
This combination of action links and context and page context can be put together in any number of ways.
...
In this pattern, the ProductListing page uses action events and a persistent field on the ProductDetails page.
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
<t:loop source="products" value="product"> <a t:type="actionlink" t:id="select" context="product.id">${product.name}</a> </t:loop> |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@InjectPage private ProductDetails details; Object onActionFromSelect(long productId) { details.setProductId(productId); return details; } |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Inject private ProductDAO dao; private Product product; @Persist private long productId; public void setProductId(long productId) { this.productId = productId; } void onActivate() { product = dao.getById(productId); } |
...
When the user clicks a link, the component event request URL will initially be something like "http://.../productlisting.select/99" and the final render request URL will be something like "http://.../productdetails". Notice that the product id ("99") does not appear in the render request URL.
It has some minor flawsThis pattern has the following drawbacks:
- It requires a session (to store the productId field between requests).
- It may fail if the ProductDetails page is accessed before a valid product id is set.
- The URL does not indicate the identity of the product; if the user bookmarks the URL and comes back later, they will trigger the previous case (no valid product id).
...
We can improve the previous example without changing the ProductListing page, using a passivation and activation context to avoid the session and make the links more bookmarkable.
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
@Inject private ProductDAO dao; private Product product; private long productId; public void setProductId(long productId) { productId = productId; } void onActivate(long productId) { this.productId = productId; product = dao.getById(productId); } long onPassivate() { return productId; } |
...
This is the most common version of this master/detail relationship.
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
<t:loop source="products" value="product"> <a t:type="pagelink" page="productdetails" context="product.id">${product.name}</a> </t:loop> |
Code Block | |||||||
---|---|---|---|---|---|---|---|
| |||||||
No code is needed to support the link. |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
@Inject private ProductDAO dao; private Product product; private long productId; void onActivate(long productId) { this.productId = productId; product = dao.getById(productId); } long onPassivate() { return productId; } |
...
At some point, persistent values make more sense. Tapestry has several persistence strategies available, including one that stores data in URL query parameters. See See Persistent Page Data for details.
Scrollbar |
---|