Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin


Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel in ("request-processing","rendering") and space = currentSpace()

In essence, a Tapestry application is a number of related pages, working together. To some degree, each page is like an application unto itself.

...

This dichotomy between component event requests and render 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.

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).

...

Main Article:  Component Events

Component event requests may take the form of hyperlinks (EventLink or ActionLink) or form submissions (Form).

...

The user will see the newly generated content in their browser. In addition, the URL in the browser's address bar will be a render request URL. Render request URLs are shorter and contain less application structure (for instance, they don't include component ids or event types). Render requests URLs are what your users will bookmark. The component event request URLs are transitory, meaningful only while the application is actively engaged, and not meant to be used in later sessions.

Code Block
public Object onAction(){
  return null;
}

When a string is returned, it is expected to be the logical name of a page (as opposed to the page's fully qualified class name). As elsewhere, the name of the page is case insensitive.

Again, a render request URL will be constructed and sent to the client as a redirect.

Code Block
public String onAction(){
  return "Index";
}

When a class is returned, it is expected to be a page class. Returning a page class from an event handler is safer for refactoring than returning a page name.

As with other response types, a render request URL will be constructed and sent to the client as a redirect.

Code Block
public Object onAction(){
  return Index.class
}

You may also return an instance of a page, rather than the name or class of a page.

...

You can also return a component within the page, but this will generate a runtime warning (unless you are doing a partial-page update via Ajax).

Code Block
@InjectPage
private Index index;

public Object onAction(){
  return index;
}

An event handler method may return a an instance of HttpError instance to send an error response to the client.

Code Block
public Object onAction(){
  return new HttpError(302, "The Error message);
}

An event handler method may return a Link instance directly. The Link is converted into a URL and a client redirect to that URL is sent to the client.

...

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:

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 + "\""

...

);
        }
    };
}

...


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.)

...

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.

...

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
java
java
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.

...

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:

...

Page activation uses Tapestry's Component Event mechanism. See See Component Events for details.

A page's activate event handler mirrors its passivate handler:

Code Block
java
java
  private Product product;
  . . .

...


  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.

...

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 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
java
java
  . . .

  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
    }
  }

  . . .

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
java
java
titleProductListing.html
  <t:loop source="products" value="product">
    <a t:type="actionlink" t:id="select" context="product.id">${product.name}</a>
  </t

...

:loop>


...

Code Block
java
java
title
ProductListing.java
  @InjectPage
  private ProductDetails details;

  Object onActionFromSelect(long productId)
  {
    details.setProductId(productId);

    return details;
  }


Code Block
java
java
titleProductDetails.java
  @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);
  }

This is a minimal approach, perhaps good enough for a prototype.

...

  • 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).

Anchor
activationpattern
activationpattern

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
java
java
titleProductDetails.java

...

  @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 change ensures that the render request URL will include the product id, i.e., "http://.../productdetails/99".

...

This is the most common version of this master/detail relationship.

Code Block
java
java
titleProductListing.html
  <t:loop source="products" value="product">
    <a t:type="pagelink" page="productdetails" context="product.id">${product.name}</a>
  </t:loop>


Code Block
java
java
titleProductListing.java
No code is needed to support the link.


Code Block
java
java
titleProductDetails.

...

java
  @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; }

The setProductId() method is no longer needed.

...

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