You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 34 Next »

General Questions

Contents

How do I get started with Tapestry?

Even without Maven, Tapestry is quite easy to set up. You just need to download the binaries and setup your build to place them inside your WAR's WEB-INF/lib folder. The rest is just some one-time configuration of the web.xml deployment descriptor.

Why does Tapestry use Prototype (in versions before 5.4)? Why not insert favorite JavaScript library here?

An important goal for Tapestry is seamless DHTML and Ajax integration. To serve that goal, it was important that the built in components be capable of Ajax operations, such as dynamically re-rendering parts of the page. Because of that, it made sense to bundle a well-known JavaScript library as part of Tapestry.

At the time (this would be 2006-ish), Prototype and Scriptaculous were well known and well documented, whereas jQuery was just getting started.

The intent has always been to make this aspect of Tapestry pluggable. Tapestry 5.4 includes the option of either Prototype or jQuery, and future versions of Tapestry will likely remove Prototype as an option..

Why does Tapestry have its own Inversion of Control Container? Why not Spring or Guice?

An Inversion of Control Container is the key piece of Tapestry's infrastructure. It is absolutely necessary to create software as robust, performant and extensible as Tapestry.

Tapestry IoC includes a number of features that distinguish itself from other containers:

  • Configured in code, not XML
  • Built-in extension mechanism for services: configurations and contributions
  • Built-in aspect oriented programming model (service decorations and advice)
  • Easy modularization
  • Best-of-breed exception reporting

Because Tapestry is implemented on top of its IoC container, and because the container makes it easy to extend or replace any service inside the container, it is possible to make the small changes to Tapestry needed to customize it to any project's needs.

In addition – and this is critical – Tapestry allows 3rd party libraries to be built that fully participate in the configurability of Tapestry itself. This means that such libraries can be configured the same way Tapestry itself is configured, and such libraries can also configure Tapestry itself. This distributed configuration requires an IOC container that fully supports such configurability.

How do I upgrade from Tapestry 4 to Tapestry 5?

There is no existing tool that supports upgrading from Tapestry 4 to Tapestry 5; Tapestry 5 is a complete rewrite.

Many of the basic concepts in Tapestry 4 are still present in Tapestry 5, but refactored, improved, streamlined, and simplified. The basic concept of pages, templates and components are largely the same. Other aspects, such as server-side event handling, is markedly different.

Tapestry 5 is designed so that it can live side-by-side in the same servlet as a Tapestry 4 app, without package namespace conflicts, sharing session data and common resources such as images and CSS. This means that you can gradually migrate a Tapestry 4 app to Tapestry 5 one page (or one portion of the app) at a time.

How do I upgrade from one version of Tapestry 5 to another?

Main Article: How to Upgrade.

A lot of effort goes into making an upgrade from one Tapestry 5 release to another go smoothly. In the general case, it is just a matter of updating the version number in your Maven build.xml or Gradle build.gradle file and executing the appropriate commands (e.g., gradle idea or mvn eclipse:eclipse) to bring your local workspace up to date with the latest binaries.

After changing dependencies, you should always perform a clean recompile of your application.

We make every effort to ensure backwards-compatibility. Tapestry is mostly coded in terms of interfaces; those interfaces are stable to a point: interfaces your code is expected to implement are usually completely frozen; interfaces your code is expected to invoke, such as the interfaces to IoC services, are stable, but may have new methods added in a release; existing methods are not changed.

In rare cases a choice is necessary between fixing bugs (or adding essential functionality) and maintaining complete backwards compatibility; in those cases, an incompatible change may be introduced. These are always discussed in detail in the Release Notes for the specific release. You should always read the release notes before attempting an upgrade, and always (really, always) be prepared to retest your application afterwards.

Note that you should be careful any time you make use of internal APIs (you can tell an API is internal by the package name, org.apache.tapestry5.internal). Internal APIs may change at any time; there's no guarantee of backwards compatibility. Please always check on the documentation, or consult the user mailing list, to see if there's a stable, public alternative. If you do make use of internal APIs, be sure to get a discussion going so that your needs can be met in the future by a stable, public API.

Why are there both Request and HttpServletRequest?

Tapestry's Request interface is very close to the standard HttpServletRequest interface. It differs in a few ways, omitting some unneeded methods, and adding a couple of new methods (such as isXHR()), as well as changing how some existing methods operate. For example, getParameterNames() returns a sorted List of Strings; HttpServletRequest returns an Enumeration, which is a very dated approach.

However, the stronger reason for Request (and the related interfaces Response and Session) is to enable the support for Portlets at some point in the future. By writing code in terms of Tapestry's Request, and not HttpServletRequest, you can be assured that the same code will operate in both Servlet Tapestry and Portlet Tapestry.

Templating and Markup

Main Article: Component Templates

Contents

Why do I get a SAXParseException when I use an HTML entity, such as   in my template?

Tapestry uses a standard SAX parser to read your templates. This means that your templates must be well formed: open and close tags must balance, attribute values must be quoted, and entities must be declared. The easiest way to accomplish this is to add a DOCTYPE to your the top of your template:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

Part of the DOCTYPE is the declaration of entities such as &nbsp;.

Alternately, you can simply use the numeric version: &#160; This is the exact same character and will render identically in the browser.

Starting in release 5.3, Tapestry introduces an XHTML doctype when no doctype is present; this means that common HTML entities will work correctly.

Why do some images in my page show up as broken links?

You have to be careful when using relative URLs inside page templates; the base URL may not always be what you expect. For example, inside your ViewUser.tml file, you may have:

  <img class="icon" src="icons/admin.png"/>${user.name} has Administrative access

This makes sense; ViewUser.tml is in the web context, as is the icons folder. The default URL for this page will be /viewuser (assuming that ViewUser class is in the  root-package.pages package).

However, the ViewUser page might use a page activation context to identify which user is to be displayed:

public class ViewUser

  @Property
  @PageActivationContext
  private User user;

  . . .

With a page activation context, the URL for the page will incorporate the ID of the User object, something like /viewuser/37371. This is why the relative URL to the admin.png image is broken: the base path is relative to the page's URL, not to the page template. (In fact, the page template may not even be in the web context, it may be stored on the classpath, as component templates are.)

One solution would be to predict what the page URL will be, and adjust the path for that:

  <img class="icon" src="../icons/admin.png"/>${user.name} has Administrative access

But this has its own problems; the page activation context may vary in length at different times, or the template in question may be a component used across many different pages, making it difficult to predict what the correct relative URL would be.

The best solution for this situation, one that will be sure to work in all pages and all components, is to make use of the context: binding prefix:

  <img class="icon" src="${context:icons/admin.png}"/>${user.name} has Administrative access

The src attribute of the <img> tag will now be bound to a dynamically computed value: the location of the image file relative to the web application context. This is especially important for components that may be used on different pages.

What's the difference between id and t:id?

You might occasionally see something like the following in a template:

<t:zone id="status" t:id="statusZone">

Why two ids? Why are they different?

The t:id attribute is the Tapestry component id. This id is unique within its immediate container. This is the id you might use to inject the component into your page class:

  @InjectComponent
  private Zone statusZone;

The other id is the client id, a unique id for the rendered element within the client-side DOM. JavaScript that needs to access the element uses this id. For example:

  $('status').hide();

In many components, the id attribute is an informal parameter; a value from the template that is blindly echoed into the output document. In other cases, the component itself has an id attribute. Often, in the latter case, the Tapestry component id is the default value for the client id.

Why do my images and stylesheets end up with a weird URLs like /assets/meta/zeea17aee26bc0cae/layout/layout.css?

Tapestry doesn't rely on the servlet container to serve up your static assets (images, stylesheets, flash movies, etc.). Instead, Tapestry processes the requests itself, streaming assets to the browser.

Asset content will be GZIP compressed (if the client supports compression, and the content is compressible). In addition, Tapestry will set a far-future expires header on the content. This means that the browser will not ask for the file again, greatly reducing network traffic.

The weird hex string is a fingerprint; it is a hash code computed from the actual content of the asset. If the asset ever changes, it will have a new fingerprint, and so will be a new path and a new (immutable) resource. This approach, combined with a far-future expires header also provided by Tapestry, ensures that clients aggressively cache assets as they navigate your site, or even between visits.

How do I add a CSS class to a Tapestry component?

As they say, "just do it". The majority of Tapestry components support informal parameters, meaning that any extra attributes in the element (in the template) will be rendered out as additional attributes. So, you can apply a CSS class or style quite easily:

  <t:textfield t:id="username" class="big-green"/>

You can even use template expansions inside the attribute value:

  <t:textfield t:id="username" class="${usernameClass}"/>

and

  public String getUsernameClass() 
  {
    return isUrgent() ? "urgent" : null;
  }

When an informal parameter is bound to null, then the attribute is not written out at all.

You can verify which components support informal parameters by checking the component reference, or looking for the @SupportsInformalParameters annotation in the components' source file.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

This page has moved to Page And Component Classes FAQ This page has moved to Forms and Form Components FAQ  

 

Link Components

Main Articles: Page Navigation, Component Parameters

Contents

How do I add query parameters to a PageLink or ActionLink?

These components do not have parameters to allow you to specify query parameters for the link; they both allow you to specify a context (one or more values to encode into the request path).

However, you can accomplish the same thing with a little code and markup. For example, to create a link to another page and pass a query parameter, you can replace your PageLink component with a standard <a> tag:

<a href="${profilePageLink}">Display Profile (w/ full details)</a>

In the matching Java class, you can create the Link programmatically:

  @Inject
  private PageRenderLinkSource linkSource;

  public Link getProfilePageLink()
  {
    Link link = linkSource.createPageRenderLinkWithContext(DisplayProfile.class, user);
    link.addParameterValue("detail", true);
    return link;
  }

... and in the DisplayProfile page:

DisplayProfile.java (partial)
public class DisplayProfile
{
  void onActivate(@RequestParameter("detail") boolean detail)
  {
    . . .
  }
}

The @RequestParameter annotation directs Tapestry to extract the query parameter from the request and coerce it to type boolean. You can use any reasonable type for such a parameter (int, long and Date are common).

A similar technique can be used to add query parmeters to component event URLs (the type generated by the ActionLink or EventLink components), by injecting the ComponentResources, and invoking method createEventLink().

Added in 5.3
You may also bind a link component's parameters parameter; this is a Map of additional query parameters to add to the URL. The Map keys should be strings, and the Map values will be encoded to strings. Tapestry 5.3 also adds a literal map syntax to the property expression language.

How do I create a Link back to the current page from a component?

Sometimes it is useful to create a link back to the current page, but you don't always know the name of the page (the link may appear inside a deeply nested subcomponent). Fortunately, this is easy.

<t:pagelink page="prop:componentResources.pageName">refresh page</t:pagelink>

Every component has an extra property, componentResources, added to it: it's the instance of ComponentResources that represents the link between your code and all of Tapestry's structure around your class. One of the properties of ComponentResources is pageName, the name of the page. By binding the PageLink's page parameter with the "prop:" binding prefix, we ensure that we bind to a computed property; this is necessary because the PageLink.page parameter defaults to the "literal:" binding prefix.

As an added benefit, if the page class is ever renamed or moved to a different package, the pageName property will automatically adjust to the new name.

The Tapestry documentation is being reorganized. For the JavaScript topic, please see one of the following pages: This page has moved to Ajax Components FAQ

Injection

Main article:  Injection

Contents

What's the difference between the @Component and @InjectComponent annotations?

The @Component annotation is used to define the type of component, and its parameter bindings. When using @Component, the template must not define the type, and any parameter bindings are merged in:

  <a t:id="home" class="nav">Back to home</a>
  @Component(parameters={ "page=index" })
  private PageLink home;

Here the type of component is defined by the field type. The field name is matched against the t:id in the template. The page parameter is set in the Java class, and the informal class parameter is set in the template. If the tag in the template was <t:pagelink>, or if the template tag included the attribute t:type="pagelink", then you would see an exception.

By contrast, @InjectComponent expects the component to be already defined, and doesn't allow any configuration of it:

  <t:form t:id="login"> .... </t:form>
  @InjectComponent
  private Form login;

Again, we're matching the field name to the component id, and you would get an error if the component is not defined in the template.

What's the difference between the @InjectPage and @InjectContainer annotations?

The @InjectPage annotation is used to inject some page in the application into a field of some other page. You often see it used from event handler methods:

  @InjectPage
  private ConfirmRegistration confirmRegistration;

  Object onSuccessFromRegistrationForm()
  {
    confirmRegistration.setStatus("Registration accepted");
    confirmRegistration.setValidationCode(userRegistrationData.getValidationCode());

    return confirmRegistration;
  }

This code pattern is used to configure peristent properties of a page before returning it; Tapestry will send a client redirect to the page to present the data.

@InjectContainer can be used inside a component or a mixin. In a component, it injects the immediate container of the component; this is often the top-level page object.

In a mixin, it injects the component to which the mixin is attached.

I get an exception because I have two services with the same interface, how do I handle this?

It's not uncommon to have two or more services that implement the exact same interface. When you inject, you might start by just identifying the type of service to inject:

	@Inject
	private ComponentEventResultProcessor processor;

Which results in the error: Service interface org.apache.tapestry5.services.ComponentEventResultProcessor is matched by 3 services: AjaxComponentEventResultProcessor, ComponentEventResultProcessor, ComponentInstanceResultProcessor. Automatic dependency resolution requires that exactly one service implement the interface.

We need more information than just the service interface type in order to identify which of the three services to inject. One possibility is to inject with the correct service id:

	@InjectService("ComponentEventResultProcessor")
	private ComponentEventResultProcessor processor;

This works ... but it is clumsy. If the service id, "ComponentEventResultProcessor", ever changes, this code will break. It's not refactoring safe.

Instead, we should use marker annotations. If we look at TapestryModule, where the ComponentEventResultProcessor service is defined, we'll see it identifies the necessary markers:

    @Marker(
    { Primary.class, Traditional.class })
    public ComponentEventResultProcessor buildComponentEventResultProcessor(
            Map<Class, ComponentEventResultProcessor> configuration)
    {
        return constructComponentEventResultProcessor(configuration);
    }

When a service has marker annotations, the annotations present at the point of injection (the field, method parameter, or constructor parameter) are used to select a matching service. The list of services that match by type is then filtered to only include services that have all of the marker annotations present at the point of injection.

    @Inject
	@Traditional @Primary
	private ComponentEventResultProcessor processor;

The two marker annotations, @Traditional and @Primary, ensure that only a single service matches.

What's the difference between @Inject and @Environmental?

@Inject is relatively general; it can be used to inject resources specific to a page or component (such as ComponentResources, Logger, or Messages), or it can inject services or other objects obtained from the Tapestry IoC container. Once the page is loaded, the values for these injections never change.

@Environmental is different; it exposes a request-scoped, dynamically bound value:

  • "Request scoped": different threads (processing different requests) will see different values when reading the field.
  • "Dynamically bound": the value is explicitly placed into the Environment, and can be overridden at any time.

Environmentals are a form of loosely connected communication between an outer component (or even a service) and an inner component. Example: the Form component places a FormSupport object into the environment. Other components, such as TextField, use the FormSupport when rendering to perform functions such as allocate unique control names or register client-side validations. The TextField doesn't require that the Form component be the immediate container component, or even an ancestor: a Form on one page may, indirectly, communicate with a TextField on some entirely different page. Neither component directly links to the other, the FormSupport is the conduit that connects them.

The term "Environmental" was chosen as the value "comes from the environment".

But wait ... I see I used the @Inject annotation and it still worked. What gives?

In certain cases, Tapestry exposes a service (which can be injected) that is a proxy to the environmental; this is primarily for common environmentals, such as JavaScriptSupport, that may be needed outside of component classes. You can see this in TapestryModule:

TapestryModule.java (partial)
    /**
     * Builds a proxy to the current {@link JavaScriptSupport} inside this thread's {@link Environment}.
     * 
     * @since 5.2.0
     */
    public JavaScriptSupport buildJavaScriptSupport()
    {
        return environmentalBuilder.build(JavaScriptSupport.class);
    }

This kind of logic is based on the EnvironmentalShadowBuilder service.

Ok, but Request is a singleton service, not an environmental, and I can inject that. Is Tapestry really thread safe?

Yes, of course Tapestry is thread safe. The Request service is another special case, as seen in TapestryModule:

TapestryModule.java (partial)
    public Request buildRequest()
    {
        return shadowBuilder.build(requestGlobals, "request", Request.class);
    }

RequestGlobals is a per-thread service. The Request service is a global singleton created by the PropertyShadowBuilder service, but is just a proxy. It has no internal state; invoking a method on the Request service just turns around and extracts the Request object from the per-thread RequestGlobals and invokes the same method there.

I use @Inject on a field to inject a service, but the field is still null, what happened?

This can happen when you use the wrong @Inject annotation; for example, com.google.inject.Inject instead of org.apache.tapestry5.ioc.annotations.Inject. This can occur when you have TestNG on the classpath, for example, and your IDE is too helpful. Double check your imports when things seem weird.

Also remember that @Inject on fields works for components and for service implementations or other objects that Tapestry instantiates, but not on arbitrary objects (that are created via Java's new keyword).

This page has moved to Tapestry Inversion of Control FAQ

Integration with existing applications

Contents

You may have an existing JSP (or Struts, Spring MVC, etc.) application that you want to migrate to Tapestry. It's quite common to do this in stages, moving some functionality into Tapestry and leaving other parts, initially, in the other system. You may need to prevent Tapestry from handling certain requests.

How do I make a form on a JSP submit into Tapestry?

Tapestry's Form component does a lot of work while an HTML form is rendering to store all the information needed to handle the form submission in a later request; this is all very specific to Tapestry and the particular construction of your pages and forms; it can't be reproduced from a JSP.

Fortunately, that isn't necessary: you can have a standard HTML Form submit to a Tapestry page, you just don't get to use all of Tapestry's built in conversion and validation logic.

All you need to know is how Tapestry converts page class names to page names (that appear in the URL). It's basically a matter of stripping off the root-package.pages prefix from the fully qualified class name. So, for example, if you are building a login screen as a JSP, you might want to have a Tapestry page to receive the user name and password. Let's assume the Tapestry page class is com.example.myapp.pages.LoginForm; the page name will be loginform (although, since Tapestry is case insensitive, LoginForm would work just as well), and the URL will be /loginform.

 

LoginForm.tml
<form method="post" action="/loginform">

  <input type="text" value="userName"/>
  <br/>
  <input type="password" value="password"/>
  <br/>
  <input type="submit" value="Login"/>

</form>

On the Tapestry side, we can expect that the LoginForm page will be activated; this means that its activate event handler will be invoked. We can leverage this, and Tapestry's RequestParameter annotation:

LoginForm.java
public class LoginForm
{
  void onActivate(@RequestParameter("userName") String userName, @RequestParameter("password") String password)
  {
     // Validate and store credentials, etc.
  }
}

The RequestParameter annotation extracts the named query parameter from the request, coerces its type from String to the parameter type (here, also String) and passes it into the method.

How do I share information between a JSP application and the Tapestry application?

From the servlet container's point of view, there's no difference between a servlet, a JSP, and an entire Tapestry application. They all share the same ServletContext, and (once created), the same HttpSession.

On the Tapestry side, it is very easy to read and write session attributes:

ShowSearchResults.java
public class ShowSearchResults
{
  @SessionAttribute
  private SearchResults searchResults;
}

Reading the instance variable searchResults is instrumented to instead read the corresponding HttpSession attribute named "searchResults". You can also specify the value attribute of the SessionAttribute annotation to override the default attribute name.

Writing to the field causes the corresponding HttpSession attribute to be modified.

The session is automatically created as needed.

How do I put the Tapestry application inside a folder, to avoid conflicts?

Support for this was added in 5.3; see the notes on the configuration page.

 

 

 

This page has moved to Specific Errors FAQ

Limitations

Contents

How do I add new components to an existing page dynamically?

The short answer here is: you don't. The long answer here is you don't have to, to get the behavior you desire.

One of Tapestry basic values is high scalability: this is expressed in a number of ways, reflecting scalability concerns within a single server, and within a cluster of servers.

Although you code Tapestry pages and components as if they were ordinary POJOs (Plain Old Java Objects -- Tapestry does not require you to extend any base classes or implement any special interfaces), as deployed by Tapestry they are closer to a traditional servlet: a single instance of each page services requests from multiple threads. Behind the scenes, Tapestry transforms you code, rewriting it on the fly.

What this means is that any incoming request must be handled by a single page instance. Therefore, Tapestry enforces the concept of static structure, dynamic behavior.

Tapestry provides quite a number of ways to vary what content is rendered, well beyond simple conditionals and loops. It is possible to "drag in" components from other pages when rendering a page (other FAQs will expand on this concept). The point is, that although a Tapestry page's structure is very rigid, the order in which the components of the page render does not have to be top to bottom.

Why doesn't my service implementation reload when I change it?

Main article: Service Implementation Reloading

Live service reloading has some limitations:

  • The service must define a service interface.
  • The service implementation must be on the file system (not inside a JAR).
  • The implementation must be instantiated by Tapestry, not inside code (even code inside a module class).
  • The service must use the default scope (reloading of perthread scopes is not supported).

Consider the following example module:

public static void bind(ServiceBinder binder)
{
  binder.bind(ArchiveService.class, ArchiveServiceImpl.class);
}

public static JobQueue buildJobQueue(MessageService messageService, Map<String,Job> configuration)
{
  JobQueueImpl service = new JobQueueImpl(configuration);

  messageService.addQueueListener(service);
 
  return service;
}

ArchiveService is reloadable, because Tapestry instantiates ArchiveServiceImpl itself. On the other hand, Tapestry invokes buildJobQueue() and it is your code inside the method that instantiates JobQueueImpl, so the JobQueue service will not be reloadable.

Finally, only classes whose class files are stored directly on the file system, and not packaged inside JARs, are ever reloadable ... generally, only the services of the application being built (and not services from libraries) will be stored on the file system. This reflects the intent of reloading: as an agile development tool, but not something to be used in deployment.

How do I run multiple Tapestry applications in the same web application?

Running multiple Tapestry 5 applications is not supported; there's only one place to identify the application root package, so even configuring multiple filters into multiple folders will not work.

Support for multiple Tapestry applications in the same web application was a specific non-goal in Tapestry 5 (it needlessly complicated Tapestry 4). Given how loosely connected Tapestry 5 pages are from each other, there doesn't seem to be an advantage to doing so ... and certainly, in terms of memory utilization, there is a significant down side, were it even possible.

You can run a Tapestry 4 app and a Tapestry 5 app side-by-side (the package names are different, for just this reason), but they know nothing of each other, and can't interact directly. This is just like the way you could have a single WAR with multiple servlets; the different applications can only communicate via URLs, or shared state in the HttpSession.

 

 

 

Hibernate Support

Main article: Hibernate

Contents

How do I get Hibernate to startup up when the application starts up, rather than lazily with the first request for the application?

This was a minor problem in 5.0; by 5.1 it is just a matter of overriding the configuration system tapestry.hibernate-early-startup to "true".

Maven Support

Contents

Why do Maven project names and other details show up in my pages?

Tapestry and maven both use the same syntax for dynamic portions of files: the ${...} syntax. When Maven is copying resources from src/main/resources, and when filtering is enabled (which is not the default), then any expansions in Tapestry templates that match against Maven project properties are substituted. If you look at the deployed application you'll see that ${name} is gone, replaced with your project's name!

The solution is to update your pom.xml and ignore any .tml files when copying and filtering:

pom.xml (partial)
  <resource>
    <directory>src/main/resources</directory>
    <excludes>
      <exclude>**/*.tml</exclude>
    </excludes>
    <filtering>true</filtering>
  </resource>

  <resource>
    <directory>src/main/resources</directory>
    <includes>
      <include>**/*.tml</include>
    </includes>
    <filtering>false</filtering>
  </resource>

Unknown macro: {htmlcomment}

Update here after adding new headings to child pages. Forces a rebuild of the main TOC.

A random number: 1234

  • No labels