Page tree
Skip to end of metadata
Go to start of metadata

About this Tutorial

This tutorial will show you how to create master/detail screens with Tapestry 5. The list (master) screen will have the ability to sort columns, as well as page 25 records at a time. The form (detail) screen will use a responsive CSS form layout (courtesy of Bootstrap). You will also configure client and server-side validation to improve your users' experience.

IntelliJ IDEA Rocks

Icon

We highly recommend using IntelliJ IDEA when developing web applications in Java. Not only is its Java and JUnit support fantastic, but it has excellent CSS and JavaScript support. Using JRebel with IDEA is likely to provide you with the most pleasant Java development experiences you've ever had.

Icon

This tutorial assumes you've created a project with the appfuse-basic-tapestry archetype and have already completed the Persistence and Services tutorials. If you're using the appfuse-modular-tapestry archetype, please morph your mind into using the web module as the root directory. If you created your project with a different web framework than Tapestry, you're likely to be confused and nothing will work in this tutorial. (wink)

Table of Contents

  1. Introduction to Tapestry
  2. Create a PersonListTest
  3. Create a PersonList class that will fetch people
  4. Create PersonList.html to show search results
  5. Create a PersonFormTest and PersonForm for edit(), save() and delete() methods
  6. Add an edit listener to PersonList.java
  7. Create PersonForm.html to edit a person
  8. Configure Validation
  9. Create a Canoo WebTest to test browser-like actions
  10. Add link to menu

Source Code

Icon

The code for this tutorial is located in the "tutorial-tapestry" module of the appfuse-demos project on GitHub. Use the following command to check it out from Subversion:

svn co https://github.com/appfuse/appfuse-demos/trunk/tutorial-tapestry

Introduction to Tapestry

Tapestry is a component-based framework for developing web applications. Unlike many other Java web frameworks, Tapestry uses a component object model similar to traditional GUI frameworks. According to Howard Lewis Ship, the founder of Tapestry:

A component is an object that fits into an overall framework; the responsibilities of the component are defined by the design and structure of the framework. A component is a component, and not simply an object, when it follows the rules of the framework. These rules can take the form of classes to inherit from, naming conventions (for classes or methods) to follow, or interfaces to implement. Components can be used within the context of the framework. The framework will act as a container for the component, controlling when the component is instantiated and initialized, and dictating when the methods of the component are invoked.

– Lewis Ship, Howard. Tapestry in Action. Greenwich, CT: Manning Publications Co., 2004.

 

The figure below shows how Tapestry fits into a web application's architecture:

Tapestry's component model allows you to have a very high level of reuse within and between projects. You can package components in JAR files and distribute them among teams and developers.

Tapestry tries to hide the Servlet API from developers. Learning Tapestry is often characterized as an "unlearning" process. GUI programmers typically have an easier time adjusting to the way things work in Tapestry. Tapestry operates in terms of objects, methods and properties, rather than URLs and query parameters. All of the URL building, page dispatching and method invocation happens transparently.

Other benefits of Tapestry include line-precise error reporting and easy-to-use HTML templates. While other frameworks use external templating systems, Tapestry has its own templating system. Tapestry templates are often HTML files, but they can also be WML or XML. You can hook into these templates by using Tapestry-specific attributes on existing HTML elements.

Create a PersonListTest 

This tutorial shows you how to create a Tapestry application using test-first development. You will use JUnit and a BasePageTestCase that instantiates page classes for you.

Create a PersonListTest.java class in src/test/java/**/webapp/pages:

This class will not compile until you create the PersonList class.

Create a PersonList that will fetch people

Create a PersonList.java file in src/main/java/**/webapp/pages:

Since Tapestry's PageTester class requires your template exists before tests will pass, please continue to the next step before running your test.

Create PersonList.tml to show search results

Create a src/main/webapp/PersonList.tml page to display the list of people.

Icon

Now if you run mvn test -Dtest=PersonListTest, your test should pass.

Nice!

BUILD SUCCESS
Total time: 11.734s

Open src/main/resources/ApplicationResources.properties and add i18n keys/values for the various "person" properties:

Run mvn jetty:run and open http://localhost:8080/personlist in your browser. Login with admin/admin and you should see a screen similar to the figure below.

Security settings for AppFuse specify that most url-patterns should be protected (except for /signup and /passwordhint). This guarantees that clients must go through Tapestry's TapestrySpringFilter to get to view pages.

CSS Customization

Icon

If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file. This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:

body#pageName element.class { background-color: blue } 

Create a PersonFormTest and PersonForm for edit(), save() and delete() methods

To start creating the detail screen, create a PersonFormTest.java class in src/test/java/**/webapp/pages:

Nothing will compile at this point; you need to create the PersonForm that you're referring to in this test.

In src/main/java/**/webapp/pages, create a PersonForm.java class and populate it with the following code:

You might notice a number of keys in this file - "person.deleted", "person.added" and "person.updated". These are all keys that need to be in your i18n bundle (ApplicationResources.properties). You should've added these at the beginning of this tutorial.

If you look at your PersonFormTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so let's add those records to our sample data file (src/test/resources/sample-data.xml). Adding it at the bottom should work - order is not important since it (currently) does not relate to any other tables. If you already have this table, make sure the 2nd record exists so testRemove() doesn't fail.

DbUnit loads this file before you run any tests, so these records will be available to your PersonFormTest class. Since Tapestry's PageTester class requires your template exists before tests will pass, please continue to the next step before running your test.

Save all your files and run the tests in PersonFormTest using the command mvn test -Dtest=PersonFormTest.

BUILD SUCCESS
Total time: 18.464s

Add an edit listener to PersonList.java 

To allow users to click on the list screen to get to the edit screen, you need to add onAdd() and onActionFromEdit() methods to PersonList.java. Open PersonListTest.java and add the following testEdit() method:

Add the aforementioned methods to PersonList.java:

Then add <p:idcell> and <a> elements to the grid component in PersonList.tml

Now you need to create the view template so you can edit a person's information.

Create PersonForm.html to edit a person 

Create a src/main/webapp/PersonForm.tml page to display the form:

Run mvn jetty:run, open your browser to http://localhost:8080/personlist, and click on the Add button.

Fill in the first name and last name fields and click the Save button. This should route you to the list screen, where a success message flashes and the new person displays in the list.

Configure Validation

To enable server-side validation, you need to add the PersonForm.java object so you can specify validation information with annotations. Also, you'll want to add a beforeRender() method to make sure there's a Person object that can be populated.

Then replace the form in PersonForm.tml with the following:

After saving all your files and running mvn jetty:run, validation should kick in when you try to save this form. To test, go to http://localhost:8080/personform and try to add a new user with no first or last name. You should see the following validation errors:

With Tapestry, client-side validation is enabled by default. You can turn it off by adding clientValidation="none" to the <form> tag in PersonForm.tml.

After saving all your files and running mvn jetty:run, client-side validation should kick in when you try to save this form. To test, go to http://localhost:8080/personform and try to add a new user with no first or last name. You should see something similar to the following screen:

Create a Canoo WebTest to test browser-like actions

The next (optional) step in this tutorial is to create a Canoo WebTest to test your UI. This step is optional, because you can run the same tests manually through your browser. Regardless, it's a good idea to automate as much of your testing as possible.

You can use the following URLs to test the different actions for adding, editing and saving a user.

WebTest Recorder

Icon

There is a WebTest Recorder Firefox plugin that allows you to record your tests, rather than manually writing them.

Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open src/test/resources/web-tests.xml and add the following XML. You'll notice that this fragment has a target named PersonTests that runs all the related tests.

To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target in src/test/resources/web-test.xml.

After adding this, you should be able to run mvn verify -Pitest and have these tests execute. If this command results in "BUILD SUCCESSFUL" - nice work!

Add link to menu

The last step is to make the list, add, edit and delete functions visible to the user. The simplest way is to add a new link to the list of links in src/main/webapp/Home.tml.

Where menu.viewPeople is an entry in src/main/resources/ApplicationResources.properties (which is auto-copied to src/main/webapp/WEB-INF/app.properties).

menu.viewPeople=View People

The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to src/main/resources/../Layout.tml:

Now if you run mvn jetty:run and go to http://localhost:8080/home, you should see something like the screenshot below.

Notice that there is a new link in your main screen (from Home.tml) and on the top in your menu bar (from Layout.tml).

That's it!
You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and Tapestry 5 - Congratulations!

Because it's so much fun to watch tests fly by and success happen, run all your tests again using mvn install -Pitest.

Happy Day!

BUILD SUCCESS
Total time: 1:18.704s