Component Classes
Wiki Markup |
---|
{float:right}
{info:title=Related Articles}
* [Page And Component Classes FAQ|Page And Component Classes]
* [Component Cheat Sheet]
* [Component Templates]
{info}
{float} |
A Component class is A component class is the class associated with a page, component or mixin in your Tapestry web application. Classes for pages, components and component mixins are all created in an identical way. They are pure POJOs (Plain Old Java Objects), typically with annotations and conventionally named methods. They are not abstract, nor do they need to extend from framework base classes.base classes or implement interfaces.
Div | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||
|
In most cases, each component class will have a corresponding component template. However, it is also possible for a component class to emit all of its markup itself, without using a template.
For Tapestry 4 Users: Component classes in Tapestry 5 are much easier than in Tapestry 4. There are no base classes to extend from, the classes are concrete (not abstract), and there's no XML file. There is still a bit of configuration in the form of Java annotations, but those now go directly onto fields of your class, rather than on abstract getters and setters.
Creating a Trivial Component
...
Creating a page and or component classes in Tapestry 5 is a breeze. There are only a few constraints:
- The classes There must be a public Java class.
- The classes class must be in the correct package (see below).
- The class must have a standard public, no-arguments constructor. (The default one provided by the compiler is fine.)
Here's a very basic component:minimal component that outputs a fixed message, using a template with a matching file name:
Section | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
In the example above, the HelloWorld class contains no code at all (except what it inherits from the Object class and what Tapestry adds invisibly).
And here's a component that does the same thing, but without needing a template:
Code Block | ||||
---|---|---|---|---|
| ||||
package org.example.myapp.components; import org.apache.tapestry5.MarkupWriter; import org.apache.tapestry5.annotations.BeginRender; public class HelloWorld { @BeginRender void renderMessage(MarkupWriter writer) { writer.write("Bonjour from HelloWorld component."); } } |
In this example, just like the first one, the component's only job is to write out a fixed message. The @BeginRender annotation is a type of component life cycle render phase annotation
, a method annotation that instructs Tapestry when and under what circumstances to invoke methods of your class.
These methods are not necessarily public; they can have any visibility access level you like (unlike in Tapestry 4). By convention they usually have package-private access level (the default).
Component Packages
Component classes must exist within an appropriate package (this is necessary for runtime code transformation and class reloading to operate).
...
- For pages, place classes in root.pages. Page names are mapped to classes within this package.
- For componentsmixins, place classes in root.componentsmixins. Component Mixin types are mapped to classes within this package.
- For mixinsother components, place classes in root.mixinscomponents. Mixin Component types are mapped to classes within this package.
In addition, it is common for an application to have base classes, often abstract base classes, that should not be directly referenced. These should not go in the pages, components or mixins packages, because they then look like valid pages, components or mixins. Instead, use the root.base package to store such base classes.
Warning |
---|
Only component classes should go in any of these controlled packages; classes representing data, or interfaces, or anything that isn't precisely a component class, must go elsewhere. Any top-level class in any of the controlled packages will be transformed at runtime. The only exception is inner classes (anonymous or not), which are loaded by the same class loader as the component class loader, but not transformed as components. |
Sub-Folders / Sub-Packages
...
Tapestry performs some simple optimizations of the logical page name (or component type, or mixin type). It checks to see if the package name is either a prefix or a suffix of the unqualified class name (case insensitively, of course) and removes the prefix or suffix if so. The net result is that a class name such as com.example.myapp.pages.user.EditUser
will have a page name of user/Edit
(not instead of user/EditUser
). The goal here is to provide shorter, more natural URLs.
Index Pages
One special simplification exists for Index pages: if the logical page name is Index after removing the package name from the unqualified class name, it will map to the root of that folder. A class such as com.example.myapp.pages.user.IndexUser
or com.example.myapp.pages.user.UserIndex
will have a page name of user/
.
In previous versions of Tapestry there was also the concept of a start page configured with the tapestry.start-page-name
configuration symbol (defaults to "start"). If a page with a name as configured with that symbol exists at the root level, this page is used as the root URL. This has precedence over an existing Index page. If for example you have a page class com.example.myapp.pages.Start
it will map to /
.
Warning |
---|
Use of start-pages is discouraged and support for it will eventually be removed. Use an Index page instead. |
Pages vs. Components
The distinction between pages and component is very, very small. The only real primary difference is the package name: root.pages.PageName for pages, and root.components.ComponentType for components. Conceptually, page components are simply the root component of a page's component tree.
...
Tapestry uses your class as a starting point. It transforms your class at runtime. This is necessary for a number of reasons, including to address how Tapestry pools shares pages between requests.
For the most part, these transformations are both sensible and invisible. In a few limited cases, they are marginally leaky – comprise a marginally leaky abstraction – for instance, the requirement that scope restrictions on instance variables be private described below – but we feel that the programming model in general will support supports a very high levels level of developer productivity.
...
However, class reloading only applies to component classes (pages, components and mixins) and, starting in 5.2, Tapestry IOC-based service implementations (with some restrictions). Other classes, such as service interfaces and implementations, or , entity/model classes, and other data objects, are loaded by the normal class loader and not subject to live class reloading. Note: In Tapestry 5.2 and later, your services can also participate in live class reloading, with some restrictions.
Instance Variables
Tapestry components may have instance variables (unlike Tapestry 4, where you had to use abstract properties).Instance variables must be private. Tapestry must perform runtime class modifications to support instance variables, and may only do so for private variables. You may have non-private variables in your class, but you may then see unexpected behavior in a production application because of how Tapestry pools and reuses pages and components. Tapestry will log an error for each component class that contains fields that are neither static nor private.
Since release 5.3.2, instance variables may be protected, or package private (that is, no access modifier). Under specific circumstances they may even be public (public fields must either be final, or have the @Retain annotation).
Be aware that you will need to either provide getter and setter methods to access your classes' instance variables. Tapestry does not do this automatically unless you provide the @Property annotation on the field, or else annotate the fields with @Property.
Transient Instance Variables
Unless an instance variable is decorated with an annotation, it will be a transient instance variable. This means that its value resets to its default value at the end of reach request (when the page is detached from the request).
Note | ||
---|---|---|
| ||
Never initialize an instance field to a mutable object at the point of declaration. If this is done, the instance created from that initializer becomes the default value for that field and is reused inside the component on every request. This could cause state to inadvertently be shared between different sessions in an application. |
Deprecated | ||
---|---|---|
| ||
For Tapestry 5.1 and earlier, in the rare event that you have a variable that can keep its value between requests and you would like to defeat that reset logic, then you |
...
can add a @Retain annotation to the field. You should take care that no client-specific data is stored into such a field, since on a later request the same page instance may be used for a different user. Likewise, on a later request for the same |
...
client, a different page instance may be used. |
Use persistent fields to hold client-specific information from one request to the next.
...
Tapestry will instantiate your class using the default, no arguments constructor. Other constructors will be ignored.
Injection
Main Article: Injection
Injection of dependencies occurs at the field level, via additional annotations. At runtime, fields that contain injections become read-only.
Code Block | ||
---|---|---|
| ||
@Inject // inject a resource
private ComponentResources componentResources;
@Inject // inject a block
private Block foo;
@Inject // inject an asset
@Path("context:images/top_banner.png")
private Asset banner;
@Inject // inject a service
private AjaxResponseRenderer ajaxResponseRenderer;
|
Parameters
Main Article: Component Parameters
Component parameters are also identified using private fields of your component class , annotated with the @Parameter annotation. Component parameters represent a two-way binding of a field of your component and a property or resource of its containing component or page.
Persistent Fields
Main Article: Persistent Page Data
...
You can define the type of component inside template, or you can create an instance variable for the component and use the @Component annotation to define the component type and parameters.
Example:
Code Block | ||
---|---|---|
| ||
package org.example.app.pages;
import org.apache.tapestry5.annotations.Component;
import org.apache.tapestry5.annotations.Property;
import org.example.app.components.Count;
public class Countdown
{
@Component(parameters =
{ "start=5", "end=1", "value=countValue" })
private Count count;
@Property
private int countValue;
}
|
...
Technically, the start and end parameters should be bound to properties, just the like the value parameter. However, certain literal values, such as the numeric literals in the example, are accepted by the prop:
binding prefix even though they are not actually properties (this is largely as a convenience to the application developer). We could also use the literal:
prefix, "start=literal:5"
, which accomplishes largely the same thing.
...
If you define a component in the component class, and there is no corresponding element in the template, Tapestry will log an error. In the example above that would be the case if the template for the Countdown page didn't contain an element with <t:count t:id="count">
.
Scrollbar |
---|