It's end-user friendly. Tapestry is designed with the security and scalability requirements in mind. Ajax, input validation, internationalization and exception reporting are built in.

It's developer friendly. Tapestry boosts the developer's productivity with unique class-reloading feature. With Tapestry you change the source code and see the results immediately. No redeploy, no restart are required! Exception reporting is very detailed and even contains suggestions.

It's web-designers friendly. Tapesry pages are valid (X)HTML! You can just open them with your favorite browser!

In this article we are going to introduce you the version 5 of the framework. We will develop a simple Create/Read/Update/Delete application using Tapestry 5 and show a few of the productivity benefits provided by Tapestry. We will describe different aspects of Tapestry applications, such as page navigation, dependency and resource injection, user input validation and application state management. You will also see how to use the Ajax functionality built in to Tapestry and how to create your own Ajax-capable components.

Optionally you can download and install Apache Maven 2.0.8. In this case you wouldn't need a separate servlet container and you could use it to build and run your Tapestry 5 application (more information see in Appendix).

We would also recommend to use a modern Integrated Development Environment (IDE) for example Eclipse or NetBeans. You can use them to edit Java and HTML files for your application.

Your first Tapestry 5 application

There are many different ways to start developing with Tapestry, one of them is to download the provided Web archive (WAR) file file from here and import it into the IDE of your choice. In case you are using Eclipse together with Web Tools you need to do following:

Choose the WAR file from your file system using the Browse... button. If not yet done, choose an installed Server Runtime Environment, for example Apache Tomcat.

Click Finish to create a Web Project from the imported WAR file

You can also use Apache Maven, in Appendix you will find detailed instructions how to start Tapestry project using quickstart archetype.

To launch the application, right click on the recently created project and select Run As>Run on Server. After the server is started, call the URL http://localhost:8080/app in your browser. The result will look like:

Now your first Tapestry application is up and running. Let's examine the directory structure.

In the source folder you will find the application's root package t5demo. Inside the application's web.xml deployment descriptor, you will find a context parameter tapestry.app-package whose value is the name of this package. Unlike nearly all Java web frameworks, Tapestry 5 doesn't require any XML configuration files. This context parameter is the sole bit of configuration you need to provide. It informs Tapestry of where to look for pages, components, and other classes needed at runtime. For example, page classes are expected to be inside the pages subpackage of our tapestry.app-package (that is, t5demo.pages). Accordingly the component classes should be placed into the t5demo.components.

Your first Tapestry page

Let's start with our first Tapestry page. A Tapestry page is a combination of a Java class and a Tapestry component template. Component templates are files with a ".tml" extension, where "tml" stands for Tapestry Markup Language. Templates for pages are stored either in the same package as page class or inside web application root folder (WebContent). Tapestry templates are well-formed XML documents; you can edit them as you would an XHTML document. Tapestry elements and attributes are defined inside the Tapestry XML namespace and, by convention, use the prefix "t:". In Tapestry, the page Start is the equivalent of an "index" page; it will be shown when the URL does not identify a specific page name.

For our example let's edit the file Start.tml. Let's throw away the initial content of this file and start fresh. Update the template with the following content:

Inside the body tag you can see the expression ${helloWorld}. This is called an expansion. In this case the expansion is used to access the property helloWorld of the page class, t5demo.pages.Start.

Now let's create the class Start in the package t5demo.pages. Unlike nearly all Java web frameworks, Tapestry doesn't force you to extend your classes from a base class, or implement a particular interface. Tapestry pages are POJOs (Plain Old Java Objects). Like any other POJOs, the page classes must be public and must have a no-arguments constructor. The only issue you have to consider is the root package of the application and its subpackages for pages, components, etc.

The template references a property named helloWorld, so let's create it. Tapestry follows Sun's JavaBeans coding specification, so we need to create an accessor method named getHelloWorld().

For the sake of demonstration let's create that but instead create a method named getHello() and see how Tapestry will react:

package t5demo.pages;

public class Start {

public String getHello(){ return "Hello World!"; }}

When you run your application you should see the standard Tapestry exception page:

The Exception page is not only reporting problem (misspelled or otherwise wrong property name) but also offers some ways to solve it by presenting a list of available properties. It has also pinpointed where the error occurs in the template, not only listing the file name and line number, but even showing you a short excerpt. This attention to the needs of actual developers is one of the areas where Tapestry really distinguishes itself.

Now we can change the Start.tml to change the expansion to ${hello}; when we refresh the page in our browser, we'll see the working page:

Here's an interesting and powerful fact: you could change the code instead and Tapestry would pick up the change to your Java class as easily as the change to the template. Tapestry doesn't force you to redeploy just to see changes to your page and component classes. This way you'll find yourself working with unprecedented high levels of productivity and low levels of frustration. Another big bonus for using Tapestry. More information about the live reloading feature of Tapestry 5 is available from the Tapestry web site.

Most often, the pages of an application will have common and consistent elements for navigation, general layout, use of CSS (Cascading Style Sheets), copyright messages, and so forth. The tradition approach is to use some form of server-side include, but that's not the Tapestry way ... the Tapestry way is to create and use a component.

Your first Tapestry component

Many Java Web Frameworks use SiteMesh or Tiles for page decoration. In Tapestry this can be accomplished by a simple component. Our first component, Layout, will be used by all our pages to wrap a common layout around the page-specific content. We choose the Blue Freedom design for our demo application and copy its markup into the a new file Layout.tml located in the package t5demo.components. We also copy its stylesheet and related images into the web application root folder (WebContent).

As you can see in the example below, the consistent content that is needed in every page (the title, the header, the footer, and so forth), is placed into this file. The element <t:body/> is used to identify where the page specific content is to be placed. When a page is rendered, the <t:body/> is replaced by the page-specific content.

The HTML elements responsible for the layout can be removed from Start's page's template. To include the common HTML we use the component Layout. We can simply use an element in the Tapestry namespace whose name matches the component type. Referencing the Layout component this way, the Start's template can be reduced to following:

Notice how the <t:layout> component wraps around the ${hello} expansion: the expansions is the page-specific content, but other pages will have forms and tables and all the other trappings of a web application. Also, you may be wondering "is it layout or Layout"? The answer is: yes. Or really, the answer is that nearly everywhere, Tapestry doesn't care about case.

Tapestry components, like pages, consist of a component template and a Java class, and it's time to create the class. You might have noticed that the template didn't indicate a stylesheet, even though most of the look and feel is derived from a stylesheet (and a few related images). That's because its easier to provide the stylesheet in the Java class than in the template! Tapestry has a special annotation for this purpose:

@IncludeStylesheet("context:style.css")public class Layout {

}

The "context:" prefix clues Tapestry in on where to look for the file. Another option is "classpath:", which is used by Tapestry components to reference files stored inside JARs ... Tapestry takes care of building a URL for the client that can reference such a file.

At this point, we're ready to leave behind make-work such as a "Hello World" message and dive into create a more realistic application. We'll start by defining the model, the data objects to be edited and displayed.

Defining your model

Now let's create a simple domain model for our example application. We will create a CRUD (Create, Retrieve, Update and Delete) like application for managing users, each user will looks like this:

Attribute id was added to identify users; it will be used as a primary key in external storage (such as a database). The Enumeration enum type describe user roles:

package t5demo.model;

public enum Role { ADMIN, USER, GUEST}

Model classes are usually persisted in a kind of persistent storage, such as database. A set of database access objects (DAO) is usually used to fetch the data from the database. In the next section we will create a DAO and see how to integrate it inside the Tapestry pages.

Tapestry IoC

Tapestry IoC (Inversion of Control) is a sub-framework of Tapestry that acts as a container for objects, known as services. Tapestry itself consists of over 120 inter-connected services, and the framework is designed to easily accommodate services specific to your application. You can think of an Inversion of Control container as a kind of streamlined version of Enterprise JavaBeans that is focused on managing the life cycle of the service objects and connecting them together. The connecting part is called Dependency Injection (DI).

The Tapestry IoC framework is inspired by the best features of well known containers such as Spring, Guice and Apache HiveMind and combines simplicity, flexibility, easy deployment, fast startup and considerable power.

Given the number of existing containers (and this is only a partial list), you might ask: why not use Spring or Guice? Why invent something new? Tapestry has requirements related to life cycle and extensibility that are not satisfied by any existing container, and so it has its own, suited specifically for its needs. You can even use Tapestry IoC separately from Tapestry. We refer the reader to the Tapestry IoC documentation for further details.

DAO (Data Access Object) is a standard pattern for accessing data from the external resources, i.e. databases. In our example application we will use a UserDAO service to retrieve our User instances. Please note that Tapestry IoC and Guice use the term service whereas Spring uses the term bean.

As you can see, we have all the basic methods you might expect for a simple DAO. The implementation of the DAO is really not interesting and relevant for the article. When implementing real applications you would use an Object-Relational mapping tool (e.g. Hibernate) and most probably will use the native Tapestry 5 Hibernate integration. For our example we will emulate the database using just a java.util.ArrayList. This way you can start the demo application without any additional configuration.

Now we need to tell Tapestry which implementation to instantiate when the service UserDAO is requested. For this purpose we will edit the AppModule class inside the application services package t5demo.services. As you remember, t5demo is the application's root package. The services subpackage should be used for services. The AppModule class is where you define services specific to you application, as well as configure services built into Tapestry. Inside the AppModule class we need to bind the interface of the UserDAO service to its implementation:

That's it, the ServiceBinder will create a binding between the UserDAO interface and the UserDAOImpl implementation. Class UserDAOImpl has a default constructor and can be instantiated using Java Reflection API. Tapestry 5 will create a new instance of UserDAOImpl and inject it as soon as any page or service will need UserDAO. As you can see, no XML configuration required. You can certainly imagine how easy it would be to create unit tests for classes using UserDAO. By default all services are singletons. Therefore only one instance of UserDAOImpl will be created, and that single instance will be shared across multiple threads and, by extension, work on behalf of multiple application users.

For our demo application we would like to pre-fill the fake database with some fake data. Therefore we will add one initialization method to our UserDAOImpl, that will be invoked inside the constructor.

Grid component

To implement this new version of the Start page we'll combine four elements:

The Layout component for the general look and feel

The Tapestry Grid component to render the table of elements

The UserDAO service to access the list of Users

The Start page itself to integrate everything else

In the previous section we described the basics of Tapestry 5 IoC. In this section we will use the service created by our AppModule. To get our UserDAO service injected we just need to declare a private member of type t5demo.services.UserDAO and mark it with the @Inject annotation. Tapestry will find the UserDAO service based on its type and link it to the page via the field.

public class Start{

@Inject private UserDAO userDAO;

public List<User> getUsers() { return userDAO.findAllUsers(); }}

The template of the page is very simple, consisting just of a single instance of the Grid component. Grid'ssource parameter is required and is used to get the data to display. In our case the source is a List<User> returned by the method getUsers().

As you can see, every single row represents a User. The values of the rows are taken from properties of the User instance and formatted automatically according to their type. For example the column 'birthday' is already formated as a date, in a locale-sensitive manner. Behind the scenes, Tapestry is doing a bit of work, such as formatting the Role enum for user-presentable display ("Guest" instead of "GUEST"). Furthermore the table is sortable. If the number of available rows in the data source exceeds a certain value, the Grid will automatically add a pager to navigate the overall data. The number of rows of data displayed on each page can be changed via Grid'srowsPerPage parameter.

By default, Grid's column order is exactly the same as order of the getters inside our User class. This order can be overridden using Grid'sreorder parameter: a comma-separated list of property names.

A property such as id is really internal, it has no meaning to the end user and should be omitted. We can leverage the Grid components' exclude parameter, specifying a list of properties to be omitted.

When the exclude parameter is applied, our Start page starts to take shape:

Later in this article we will demonstrate another very powerful Tapestry component that will generate user input forms based directly from JavaBeans. To provide a consistent view of our domain model we should use another approach for removing properties from the user interface. We will identify properties that should not be part of the user interface with the @NonVisual annotation. This meta-information will be recognized not only by Grid but also by several other Tapestry components (Please see the BeanEditForm component section). In our example we only remove the identifier of the User.

For our next customization, we'll add the ability to edit a user. For this purpose we will create a page, which we discuss later. In order to create an edit link we will override how the Grid component renders one column. Instead of simply rendering out the User's name property, we'll render a link to another page responsible for editing Users.

This is accomplished with another special template element, <t:parameter>. This element is, effectively, a way to pass a chunk of a component template into a component as a parameter.

The name of the <t:parameter> element is used to determine the property to override the rendering for. Per convention the name should be composed of the property name and the suffix Cell (it is also possible to override the column headers with a Header suffix).

In our example, the <t:parameter> element name is nameCell, so the Grid component will use the provided PageLink component to render the column containing each User's name.

The PageLink component renders an HTML link to the page specified by the page parameter; additional information needed by the indicated page is provided in the context parameter. For this example , the page for editing a User will be called Edit and will accept a user ID as parameter. We will also need a property to store the User being rendered, so that we can pull out its name and id properties. We will need to create a property named user in our Start class. We can then bind the Grid component's row parameter to this property.

You can see here that expansions and other property expressions are not limited to a simple property name. You may use dotted notation to navigate one or more properties, to read (and as we'll see later, edit) a nested property.

And Tapestry will complain if there isn't at least a class for the Edit page:

public class Edit {}

We can fill in the details of the Edit page later.

We are now nearly done with the Start page:

What if we want to add another column that doesn't correspond to a property of a User? For example, we may want a column so that we can delete unwanted users. This can be accomplished using a virtual property, but it takes a smidgen more work.

The Grid component decides how to display a bean, such as a User object, by first generating a BeanModel for the object. By creating and then customizing the BeanModel ourselves, we can have total control over how the Grid operates. We must inject new objects into the Start page:

org.apache.tapestry.services.BeanModelSource - a service responsible for creation of BeanModels for a particular bean class.

org.apache.tapestry.ComponentResources - provides some framework functionality that is needed by BeanModelSource.

In the Start page's template we have two further changes: first, to tell the Grid component explicitly about the model it should use, and second, to provide a <t:parameter> block to render the delete column.

We'll use an ActionLink component to find out when the user wants to delete a user. An ActionLink will trigger an event on the Start page. We can observe the event by providing a specially named method. Providing a method name onActionFromDelete() informs Tapestry that the method should be invoked when an "action" event is triggered in the component whose id is "delete".

But which User to delete? Once again, we'll pass the id as context; the value will then be available as a method parameter to the event handler method:

Event handler methods do not have to be public; the preferred visibility is package private (the visibility provided with no modifier). Such a method can be invoked by Tapestry, and by other classes within the same package (such as unit tests), but is not part of the page's public API.

We have now finished the Start page and can move on to the Edit page:

Navigation patterns

As you remember, we need to implement a page where Users can be edited. Let's name the page Edit. In this section we describe how to navigate from the Start page to Edit page. For this purpose, let's create a template Edit.tml in the web application root folder (we already created an empty class, t5demo.pages.Edit). In this section we will describe navigation logic in Tapestry 5. The editing logic follows in the next section.

Since our page will edit an user, we would need an access to the UserDAO. To inject it, we create a private variable and mark it with @Inject annotation as before:

public class Edit {

@Inject private UserDAO userDAO; ...}

Our edit page needs to know the identifier of the User to edit. This kind of navigation pattern (often called master/detail) is quite common when developing web applications. Tapestry 5 has an explicit support for it using RESTfull page activation context mechanism.

REST stands for Representational State Transfer . It is a style of software architecture of distributed hypermedia systems. The REST's central principle is resource which is identified by URI, to modify the resource clients are communicating via standard interface (e.g. HTTP) and exchange representation of these resources. More on advantages of REST you can read on Wikipedia, in our case it's important to see that REST encourage us to make application state part of URL, and that's where the page activation context will help us.

All pages in Tapestry may support a page activation context. The page activation context contains the state of the page that can be preserved across the requests. According to REST paradigm, the page activation context is appended to the URL for the page. Page activation context is very similar to storing the page state inside the HTTP Session. However in this case you would need an active session and page state is not bookmarkable.

In Tapestry, pages are actively involved in providing the page activation context, and equally involved in processing that activation context. This is handled by providing event handler methods for two events: "passivate" and "activate". For the "passivate" event, the page should provide a value (or an array of values) that represent its complete internal state. For the Edit page, that is the id of the User being edited. When a later request (such as a form submission) for that page is processed, the page activation state will be provided, via the "activate" method, and the page will restore its full state from the provided value.

Inside the onActivate() method we expect a single long value as our page activation context, and inside the onPassivate() method we provide that exact value. On activation, the Edit page uses the injected UserDAO service to fetch the User from the database and stores it inside the private variable.

Now let's have a look at the URLs of the pages where context is presented. If you navigate to the first page of our demo application, you will see that URL to edit a User has the following form: http://localhost:8080/app/edit/3. Taking into account that all page names in Tapestry are case insensitive, you can see that this URL will render the Edit page and the User to edit has the identifier 3. The URL is plain and simple; it is RESTfull and therefore it is bookmarkable (we could store this URL in our browser's bookmarks and come back to the exact same page tomorrow, which would not be possible if any of the state were stored in the HttpSession).

Now that we have the ability to track the User to be edited, our next task is to provide a user interface. Doing so is surprisingly simple.

BeanEditForm component

In the previous chapter we started to work on the page where existing users can be created and/or modified. We created a page class that is able to maintain it's state and knows about the user to be edited. As in majority of the component oriented web frameworks, we need to create a form component and nested fields. Each value of the model object shall be bound to the field of the appropriate type (text fields for short text, checkboxes for booleans, drop down lists for enumerated types, and so forth). We also want to consider input validation. That's the usual way, but Tapestry has an easier way.

First of all we need to create a public getter method for the user private variable. As you remember, this variable is initialized as soon as page has restored its state from the page activation context:

public class Edit { ... public User getUser() { return user; } ...}

That wasn't very hard so far. Was it? Now it's event simpler, we need to add following line to the template:

We are using Tapestry component called BeanEditForm. It has only one required parameter object that should be bound to the property with object we want to edit. That's all. Now you can refresh the Edit page and see that it was filled with automatically generated form that contains fields for each property of the edited model class.

Tapestry 5 is again doing quite a bit of work to simplify the developer's life. Right off the bat we have a useable user interface and, as with the Grid component, lots of room for customization. This form reflects little details; field labels are properly transformed from camel case ("userName" will be shown as "User Name"), enumerated values are rendered as a drop down list with correct set of options (also transformed for readability), and a popup calendar is used for editing properties that are dates. The BeanEditForm also respects the @NonVisual annotation, so the id field is ommitted. Because both the Grid and the BeanEditForm components work off the same BeanModel they will continue to be consistent with each other.

The BeanEditForm component is very flexible and provides a lot of customization possibilities:

You can reorder fields inside the generated form by simple reordering of getters inside your source code, or programmatically.

You can hide fields using @NonVisual annotation or comma-separated list of properties provided in component parameter.

You can completely replace some parts of the generated form with custom blocks to render some input fields differently (for example password field).

You can dynamically modify meta-information for the edited bean, the same way as it was done for the Grid before.

You can change labels on the form and off course you get a full internationalization support.

You add/remove buttons inside the form.

Second step in our editing form example is validation. Since we are using automated generation of the forms, the best place for validation constraints is our model object. To add a validation constraint, just annotate the access methods of the particular field with @Validate annotation.

In our example we require only name and email properties to be filled in. The @Validate annotation accepts a string parameter that will describe the validation constraints. The syntax of this parameter might be familiar for Tapestry 4 users, essentially you just list constraints inside the comma-separated list. Possible constraint names are required, max, min, maxLength, minLength and regexp. The regular expression validator needs two parameters: the expression and the human readable message to be displayed when validation fails. These two validation parameters are both pretty long and a bad fit for the template because of all the special characters. Fortunately, Tapestry pages and applications can have a message catalog, a natural place to store long, complex strings. For this purpose we create a file Edit.properties in the package t5demo.pages. As you can see, the key of the expression is email-regexp. This key is composed of the property name to be validated and the validator name. The key of the message contains a further suffix -message.

email-regexp=^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$ email-regexp-message=Provided email is not valid

By default all forms in Tapestry have client side validation enabled. If you try to submit a form with an empty field that is required, you'll immediately see a message in a bubble like in the following figure:

Obviously client side validation is duplicated with server-side one. When you disable javascript, you will see the error message appearing after the form submit. The error message in the following figure is generated on the server side:

Once the user has submitted the form on the client side, the action picks up on the server side. Assuming there are no validation errors, the BeanEditForm component will fire a "success" (as in, a successful form submission) event. Once again we provide an event handler method, onSuccess() (it will match any component that triggers a "success" event, but the BeanEditForm is the only candidate, so it's OK to not be more specific). This time, we are not only going to perform an action (update the User back into the database) but also navigate back to the Start page.

Tapestry interprets the return value on an event handler method in a special way: as a directive of which page should render the response to the user. Returning a page instance, or a page class, or even the name of a page (as a String) navigates to that appropriate page. Other return values are supported as well (see the Tapestry documentation for a complete list).

The validations built into Tapestry are limited: they operate on both the client and on the server, but they are limited in scope. They are syntactic, they are based just on the characters provided. Say we want to perform some validation related to business logic, for example, to ensure that when a User name is edited, the new name is unique.

Again, Tapestry works with our code by firing an event; this event is named "validateForm" as is fired before the "success" event.

As you can see, we can use BeanEditForm.recordError() to add errors to the form programmatically.

Now, after the editing of the existing user works, we just need to think about the creation of new users. BeanEditForm component has a very nice behavior: when edited property is null it will just create a new instance of the bounded class and set it back to the page. Using this behavior we just need to add a public setter for the user variable. When page is activated, we need to take care when passed in identifier is 0.

That's about it. Now when we will pass a user identifier to the Edit page, it will edit the user. If the identifier is not passed or passed identifier is equals to zero, then Edit page will simply create a new instance of User.

Ajax in Tapestry 5

Ajax stands for Asynchronous JavaScript and XML. Ajax is a special technique that is used for creation of dynamic and more user friendly web sites and web applications. With Ajax, responsiveness and interactivity of the web pages is achieved by exchanging data asynchronously between the client and the server. Ajax is very powerful technique, more about it you can read on Wikipedia

Tapestry Ajax support is based on popular Prototype and script.aculo.us JavaScript libraries. Since these two libraries are bundled inside Tapestry, no separate deployment or configuration is needed.

Usually, inside Ajax-enabled applications, some parts of a page are refreshed on user request. In Tapestry, ActionLink component may be used to trigger Ajax actions. As an example we'll customize the Grid component once again by adding a further column containing links to view user's details. Let's update the model of our Grid by adding a column named view, which will be will be rendered as an ActionLink. First we add the new artificial property to our BeanModel:

As already mentioned, ActionLink is a component that triggers an action on the server side. Per default the triggered action causes a full refresh of the page. The parameter zone tells ActionLink to make an Ajax call and to update a zone, whose client id is the value of the parameter. A Zone is Tapestry's approach to perform partial updates to the client side. Zone component marks a part of the page that can be dynamically updated. Let's first create a Zone with the client id viewZone.

The content to update the Zone with, can be defined by a <t:block>. A <t:block> is a kind of a free-floating <t:parameter> that can be injected into a component. The content inside a block is not rendered by default. We create a block with the id userDetails and display some properties of the user stored into the page's property detailUser inside this block.

When ActionLink view is clicked, an action event is triggered. Accordingly, the matching method onActionFromView is called. The method parameter represents the user identifier and is used to find the user in the database. The retrieved user is stored into the private property detailUser, which we use inside a <t:block>.

As described above, the return type of an action method is used to determine the type of the response. In a traditional non-Ajax request the return type of the action method is used to determine the page to render the response. In order to perform a partial response, we may return a component or a block whose markup will be used to update the content of our Zone without to refresh the whole page. For this purpose we inject the recently created block via @Inject annotation. Note that for injection to work, property name property class should be the same as component client id and component class (otherwise you can customize @Inject annotation). The injected block is returned by our action method onActionFromView. The block is rendered and its markup is inserted into the Zone.

Here we demostrated the basic Ajax functionality - partial page refresh. With Tapestry 5 such functionality is very easy to implement and you don't need to write a line of JavaScript on your own. Functionality provided by embedded JavaScript libraries is cross-browser compatible production-ready.

Creating your own ajax component

Obviously full interactivity in Web application can not be achieved only using partial page refreshes, now we want to see how to create our own Ajax component. In this section we will create a Tapestry 5 component that will use Ajax.InPlaceEditor from script.aculo.us JavaScript library. InPlaceEditor allows you to edit some text on the page. Clicking that text creates an input field on the fly, whose value is the text we clicked on. Submitting the form we replace the original text on the page by the value of the field.

First we inject the JavaScript file with InPlaceEditor. We do it in a similar way we included our Cascading Style Sheets into the Layout - using annotation @IncludeJavaScriptLibrary. Please note, that if inside a page you are using a bundle of components including the same JavaScript file via @IncludeJavaScriptLibrary, you don't have to worry about the repeated inclusion of the particular file. Tapestry includes every single resource only once.

Now let's write a method to render the component. The rendering of Tapestry components is divided into several phases. We did not mention these phases so far. Since we want to keep the article simple, we will not discuss them all in this article. We refer you to the Tapestry documentation, if you want to learn more about them.

To render our component we plug into the phase AfterRender. This phase is usually used in combination with BeginRender to render a html tag and to decorate the component's template. Since our component has neither a tag nor a template, AfterRender will do it. To plug into the phase AfterRender we implement the method afterRender(MarkupWriter). First we try to get the element name used in the markup. If there is no element name defined, we take <span/>. Then we allocate a unique id for our component. In the next step we open the html element and render the informal parameters. Informal parameters are parameters which weren't defined by our component but were provided by the user inside the template.The only one formal parameter (specified for our component) is value. An example of a typical informal parameter is style="someCssStyle", we are not usually interested in parsing user provided styles however we would like to preserve it when we will render our component. After writing the value we close the element recently opened. Finally we write the JavaScript code to create a Ajax.InPlaceEditor.

The constructor of JavaScript "class" Ajax.InPlaceEditor takes three parameters:

The first is the client id of the element to support in-place editing.

The second is the URL to submit the changed value to.

The third is a JSON object containing options.

In the previous section we learned the ActionLink component that we used to trigger server side action. In this example we use the service ComponentResources to create an ActionLink programmatically. When the link is clicked, it will fire the event edit. This event name will be used to match the corresponding method to invoke when the event is triggered. The JSONObject passed to InPlaceEditor constructor contains only one key/value pair, which is used to name the request parameter containing the value submitted by Ajax.InPlaceEditor. Note that PageRenderSupport is used to add the JavaScript code into the page. This service will add a callback JavaScript function into the page, that will be invoked when the page DOM is loaded. The resulting HTML you will see later but now is full method code:

Finally we provide the action method to match the triggered event. When the ActionLink is clicked, it will fire the event edit. Accordingly our action method should be named onEdit. In this method we get the submitted value from the specified request parameter and assign it to the bounded property. To update the client side with the new value, we return a TextStreamResponse which provides a stream of data to be sent to the client. There are further types that may be returned to update the client.

Component or block. In the previous section we used a block to update a Zone.

Let's create an example page using InPlaceEditor. Let edit be the property of the page to be edited. Marking the field by @Persist annotation we tell Tapestry to persist the value of the field from one request to the next. Furthermore we create public getter/setter methods.

Just type some text and hit the button Ok. The property edit will be updated by the submitted value. Of course we should add further functionality to this component. For example we could add the validation of the editor field. But the intent of the article is to give you an overview of the Tapestry functionality, rather then to develop a full set of components, that are ready to be used in production.

Conclusion

We showed how simple it is to create a Tapestry application from scratch and how quick and productive you can be when using Tapestry components. We have calculated a number of non-commenting lines of code for our application using Java NCSS Plugin for Maven and the results were following:

Templates for our pages are 56 lines whereby 34 lines are in Layout.tml where page design is stored.

Two longest Java classes are UserDAOImpl and User, they are 68 lines together.

Largest page class is t5demo.pages.Start with only 27 lines of Java code and 12 lines of template code. Using Tapestry 5 we've managed to provide a sortable and pageable table with delete and view functionality. We were able to use Ajax for partial page rendering and that's all only using 39 lines of code.

Obviously, number of lines of code is not the only measure we should use. But it's clear that the more lines of code you have, the more challenging is your application to maintain, to understand and to test.

Significant efforts were spent by Tapestry team to improve the developer-frendlieness of the framework:

Tapestry 5 applications require almost zero configuration. For most of the parts, you usually need to configure, there are already sensible defaults. As you have seen before, we haven't created any line of XML in our example.

Unique class reloading feature boost development productivity. You no longer needed to restart you application container every time you found a small bug in the page or component's code. Pages and component classes are reloaded automatically.

Developer friendly error reporting not only showing the line number and code sniplet, but also suggesting the possible solutions.

Tapestry 5 gathered best practices from many application areas, such as convention over configuration and scaffolding inspired by Ruby on Rails, XML-less dependency injection and configuration inspired by Google Guice, REST-full URLs for scalability and many others. Output produced by Tapestry is valid (X)HTML. Complex components such as Grid and BeanEditForm are accessible, table-less and CSS customizable. Default output encoding is UTF-8 and localization is supported by the framework.

In our article we've shown only a small part of new Tapestry 5 features. We haven't covered Tapestry IoC to deep, localization of Tapestry applications, mixins and assets. We haven't mentioned Tapestry 5 integration modules for Hibernate, Spring, Seam, Acegi and many others. However, we hope we could show you how simple is Tapestry 5 development and how fast you can build production-ready and robust applications.

Acknowledgement

We would like to thank Howard M. Lewis Ship, creator of Tapestry, for reviewing and proof-reading this article. We appreciate the incredibly valuable feedback, he provided. Furthermore we would like to thank him for creating Tapestry. And last, but not least we would like to thank the whole Tapestry team and all the people managing open-source projects around Tapestry for amazing job.

Quickstart & Demo application

The source code of the Tapestry Quickstart and the Demo application we developed for this article can be downloaded from tapestry4nonbelievers project hosted on Google Code. Both applications are packaged as WAR files and can be imported into the IDE of you choice.

References

The most simple way to start building a Tapestry application is to use Maven and the Tapestry Quickstart archetype. A Maven archetype is a predefined project template that can be used to start a new project quickly. The Tapestry quickstart archetype defines a project directory structure, initial meta-information and even some basic application classes. If you are familiar with Apache Struts then you might be aware of the struts-blank.war file that is delivered with the Struts distribution. This file contains an empty Struts application that is fully deployable and functional.

The Tapestry 5 Maven archetype delivers an empty Tapestry application consisting of a single page, ready to be compiled and deployed to a servlet container. If you have Maven installed just execute the following command:

Maven uses the archetype to create the Tapestry application name app. Maven creates a subdirectory, app to store the new project. Let's examine what Maven has generated for you. If you are familiar with Maven you will recognize the default Maven directory structure.

To start the application using the Jetty servlet container, change to the newly created app directory and execute the following command:

mvn jetty:run

Maven will compile the generated application, download (just the first time!) the Jetty servlet container, and launch the compiled application. Once the application is launched you can access it in your browser as http://localhost:8080/app.

Unfortunately, Maven doesn't know to recompile Java classes while it is running Jetty. To benefit from the live reloading feature of Tapestry 5, you'll need to set up your IDE with an appropriate servlet support.

This is my first hands-on experience with Tapestry, although I've heard of it before. From this article, looks like it is incredible. I'm currently evaluating web frameworks for building an inventory application around a Spring-driven backend implemented with Hibernate. You made me re-consider the options :)

The article does not include the full source code of Edit.java (I've pasted it below for discussion). Just a couple of thoughts here: I'm not a big fan of annotations and I'm also not very comfortable with having to remember to implement 'magic' methods such as onActivate, onPassivate, onSuccess and onValidateForm.

So - saying that Tapestry pages just need to be POJOs is a little misleading IMHO.

This article was written a ways back (I helped with the editting) and in the meantime Tapestry 5 has rolled along; had this article been written today and for Hibernate, you'd see that Tapestry takes care of all the fiddly entity-to-id-and-back logic that is being done in explicit code here.

I define "plain" as no inheritance imposition, no visibility imposition, no interface imposition. Just the object, easily testable. Tapestry is pretty close to that (variables have to be private, though). In the article, the event handler methods were public, but I generally make them package private (no modifier). The end result? The page class is almost entirely focused on your business objects and services, with a minimal amount of framework clutter.

Java has really beat people down, language-wise, where they look at a problem, such as the way compiler makes you sit up and beg rather than being actually helpful (i.e., error messages like "you must put this cast there even though I know what you wanted") and see it as a <em>feature</em>.

Tapestry is about having the environment kowtow to <em>your</em> code instead of the other way around, which is more <em>traditional</em>. Knowing <em>what to name your method</em> (or which annotation to use) is no different than knowing <em>which interface to implement</em> or <em>which base class to extend</em>; but the Tapestry approach does a better job of encapsulation and keeps things smaller and easier to test.

* You had to extend from a framework base class? * You had to implement a specific interface?

Of course not. Quoting from the article below:

Unlike nearly all Java web frameworks, Tapestry doesn't force you to extend your classes from a base class, or implement a particular interface. Tapestry pages are POJOs (Plain Old Java Objects). Like any other POJOs, the page classes must be public and must have a no-arguments constructor. The only issue you have to consider is the root package of the application and its subpackages for pages, components, etc.

I am just pointing out that the above statement gives the (IMHO false) impression that Tapestry page components are lighter than they really are. Having a bunch of org.apache.tapestry.* imports does not feel very POJO-like to me. And also there are these onActivate, onPassivate, onSuccess and onValidateForm framework methods.

The page class is almost entirely focused on your business objects and services, with a minimal amount of framework clutter.

Looking at the page code I posted above - I'm not really convinced.

Java has really beat people down, language-wise, where they look at a problem, such as the way compiler makes you sit up and beg rather than being actually helpful (i.e., error messages like "you must put this cast there even though I know what you wanted") and see it as a feature.

Huh? So you are saying that the compiler ensuring type safety and reminding you to implement a method is a bad thing?

If I really wanted the environment to "kowtow" to my code I would have switched to Ruby or something long back. What would happen if the user mis-spelled onSuccess as 'onSucess' or something?

Since event handlers are always optional, Tapestry can't tell the difference between a misspelling and no event handler at all. Misspell "onSuccess" and your success event handler is not invoked and Tapestry's default behavior (redisplaying the page) is used instead. You'll notice that very quickly.

Anyone whose looked at my code knows that I'm a stickler for types, interfaces and contracts (and I have a tens of thousands of lines of code available to back that statement up. However, Tapestry 5 uses a looser, more adaptive sense of contract than traditional interfaces. It sure beats having endless configuration in XML (especially fully qualified class names and method names), and relative to other frameworks, the amount of code you write ends up being quite minuscule. Every tool has a learning curve, but the Tapestry 5 curve is very shallow for beginners and very rich for experts.

I noticed a handful of things that would make this even better (in decreasing priority):1. Show the reader what you're going to build before you build it. You nailed it on "Your first Tapestry component" (picture first, text below) and started falling away from that later. It's too easy for readers to get lost in the code details if you don't show the context first. 2. Include a link to a working zip (or WAR) file after each step. One small misstep and a newbie could become completely lost. Remember that the Tapestry docs aren't 100% there yet.3. Include details on how to run the app the first time (e.g. mvn jetty:run) and how to configure so automatic class, page, and component reloading works. 4. When the reader needs to download something external (e.g. the layout package), please provide instructions.5. Make sure the code samples you give (e.g. Layout.tml) generate output that match your screen shots. I had to alter the HTML code to get something reasonably correct.

Let me repeat this: you did a great job. I'm just being passionate about ease-of-learning (ask Howard about my review of a class he taught... ;))

One of the problems Howard found with previous versions of Tapestry was that using subclassing in components exposed the page developer to the internals of Tapestry. This caused problems when the framework was upgraded and restricted the scope of changes that could be made to Tapestry.

By using a naming convention (or alternatively, annotations) the Tapestry developers can decouple the framework internals from the user's pages and components, and only a subset of the framework API should be required. Howard has made this distinction explicit by creating a org.apache.tapestry.internal.* package. Any users importing these classes should be aware that the implementation may change from version to version of Tapestry and their use should be minimised.

Moreover, as Tapestry 5 evolves more features can be added transparently and the user needs to simply add methods, import new annotations or @Inject objects using IOC.

Pages are POJO's in the sense that they are standalone classes, not that they are your business domain objects. They are still very much tied to the Tapestry framework and the page lifecycle, but it is achieved by convention rather than overriding methods and implementing specific interfaces.

It's true that you can fall afoul of typos and then get hit at runtime, but the immediate class reloading means this doesn't cost much in time.

Since event handlers are always optional, Tapestry can't tell the difference between a misspelling and no event handler at all.

Just wondering if you can't have both: The framework supports plain POJO's, but wouldn't it be possible to provide some optional interfaces for at least the Pages/Components/AppModules, maybe even in a separate jar-file. That way, the people that want type-safety can still have it.

You can also throw in a couple of abstract classes with some convenient methods for replacing existing services from their AppModule without relying on naming-conventions etc.

It might help beginners getting up to speed (when they still need all the compiler/IDE-support they can get) without getting in the way of advanced Tapestry users.

Anyone whose looked at my code knows that I'm a stickler for types, interfaces and contracts (and I have a tens of thousands of lines of code available to back that statement up. However, Tapestry 5 uses a looser, more adaptive sense of contract than traditional interfaces.

@Howard: yes, for a moment I was surprised that you of all people would start knocking the Java compiler, static-typing and all. I think you have made it clear about the direction Tapestry 5 has taken. I'm not very sure that the pros outweigh the cons for end-users, but thanks for clarifying.

I'm curious whether you considered using annotations to identify the callback methods (I think Spring MVC does something like this now) so developers are free to choose a name and perhaps Tapestry-aware IDE support would be tighter in the future. Or maybe that would have introduced an unwanted explosion of annotations like @OnSuccess @OnPassivate etc?

Tapestry does that already. You can use preferred method-names or use your own method-names and then annotate them.My suggestion would be to provide optional interfaces that specify the preferred method names of all render phases. That way you have type-safety for users that want it, without them having to know the preferred names or the annotations.

Tapestry 5 already does that. If you use specific method-names Tapestry 5 picks them up, but if you want to use different method-names, you can annotate them.

I did a quick search but could not find any documentation on this. Do you have a link? Can't help thinking now what would happen if you have a valid method and an annotation. Now will some precedence rules kick in or what?

"Each of the orange phases (SetupRender, BeginRender, BeforeRenderBody, etc.) corresponds to an annotation you may place on one or more methods of your class. The annotation directs Tapestry to invoke your method as part of that phase."

It would be nice if you could provide a more mavenized structure for the project, i.e: the java files should be in main/java and template files should be in /main/resource.This is the recommended structure from what I read on tapestry.apache.org doc.

What you are seeing is evolution; the annotations came first, but as I gradually turned on to convention over configuration, I found I preferred the naming conventions to the annotations where ever possible. If I was writing the documentation from scratch, naming conventions would come first and annotations would be more of a footnote.

I guess having some @onactivate and @onpassivate annotations would be interesting.For instance one could add these annotations to the getter and the setter of an Id. Maybe one could use the setuprender, to do some some dao/service work.

@HLS : For what reasons do you prefer naming conventions over annotations?

The structure of Tapestry is such that @OnActivate and @OnPassivate could be added without changing any internal implementations in Tapestry; these would be recognized by a new, contributed ComponentClassTransformWorker. There's an outstanding issue about this (actually, I'd prefer to put an annotation on a field and let Tapestry provide the activate/passivate event handling logic around the field instead). The point is not that I'm lazy, but that Tapestry's extensible architecture means you don't have to wait for the Tapestry team to implement extensions, you're on equal footing.

I prefer naming conventions because I find them more concise and readable than annotations, once you know what to look for.

Great article, could use some clarification re where to put component stuff
by
Norris Merritt

I ran into some strange render-time exceptions which turned out to be project structure issues. The maven archetype doesn't set you up for components, so getting all the layout/css pieces in the right places can be challenging for a newbie. So far I'm very impressed with Tapestry 5 though.

InfoQ Weekly Newsletter

Join a community of over 250 K senior developers by signing up for our newsletter. If you are based in the EEA, please contact us so we can provide you with the protections afforded to you under EEA protection laws.

InfoQ Weekly Newsletter

Join a community of over 250 K senior developers by signing up for our newsletter. If you are based in the EEA, please contact us so we can provide you with the protections afforded to you under EEA protection laws.

Is your profile up-to-date? Please take a moment to review and update.

Email Address

Note: If updating/changing your email, a validation request will be sent

Company name:

Keep current company name

Update Company name to:

Company role:

Keep current company role

Update company role to:

Company size:

Keep current company Size

Update company size to:

Country/Zone:

Keep current country/zone

Update country/zone to:

State/Province/Region:

Keep current state/province/region

Update state/province/region to:

Subscribe to our newsletter?

Subscribe to our architect newsletter?

Subscribe to our industry email notices?

By subscribing to this email, we may send you content based on your previous topic interests. See our privacy notice for details.

You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.

We notice you're using an ad blocker

We understand why you use ad blockers. However to keep InfoQ free we need your support. InfoQ will not provide your data to third parties without individual opt-in consent. We only work with advertisers relevant to our readers. Please consider whitelisting us.