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|
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.
Table of Contents
- Introduction to Tapestry
- Create a PersonListTest
- Create a PersonList class that will fetch people
- Create PersonList.html to show search results
- Create a PersonFormTest and PersonForm for edit(), save() and delete() methods
- Add an edit listener to PersonList.java
- Create PersonForm.html to edit a person
- Configure Validation
- Create a Canoo WebTest to test browser-like actions
- Add link to menu
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.
PersonListTest.java class in src/test/java/**/webapp/pages:
This class will not compile until you create the
Create a PersonList that will fetch people
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.
Now if you run mvn test -Dtest=PersonListTest, your test should pass.
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.
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:
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 that extends AppFuse's
BasePage. 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.
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
onActionFromEdit() methods to
PersonListTest.java and add the following
Add the aforementioned methods to
Then add <p:idcell> and <a> elements to the grid component in
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.
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="false to the <form> tag in
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.
- Add - http://localhost:8080/personform.
- Edit - Go to http://localhost:8080/personlist and click on an existing record.
- Delete - Use the edit link above and click on the Delete button.
- Save - Use the edit link above and click the Save button.
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 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/MainMenu.tml.
menu.viewPeople is an entry in src/main/resources/ApplicationResources.properties (which is auto-copied to src/main/webapp/WEB-INF/app.properties).
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).
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.
Happy Day!BUILD SUCCESS
Total time: 1:18.704s