About this Tutorial
This tutorial creates master/detail screens with Java Server Faces
(JSF). The JSF implementation used in AppFuse is MyFaces
, but you should be able to use any 1.1+ JSF implementation. 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 nifty CSS form layout (courtesy of Wufoo
). You will also configure client and server-side validation to improve your users' experience.
 | This tutorial assumes you've created a project with the appfuse-basic-jsf archetype and have already completed the Persistence and Services tutorials. If you're using the appfuse-modular-jsf 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 JSF, you're likely to be confused and nothing will work in this tutorial. |
Table of Contents

- Introduction to JSF
- Create a PersonListTest
- Create a PersonList class that will fetch people
- Create persons.xhtml to show search results
- Create a PersonFormTest and PersonForm for edit(), save() and delete() methods
- Create personForm.xhtml to edit a person
- Configure Validation
- Create a Canoo WebTest to test browser-like actions
- Add link to menu
 | Source Code
The code for this tutorial is located in the "tutorial-jsf" module of the appfuse-demos project on Google Code. Use the following command to check it out from Subversion:
|
Introduction to JSF
JavaServer Faces (JSF) is a component-based, event-driven web framework. According to Sun Microsystem's JSF Overview
, JSF technology includes:
- A set of APIs for representing UI components and managing their state, handling events and input validation, defining page navigation, and supporting internationalization and accessibility.
- A JavaServer Pages (JSP) custom tag library for expressing a JavaServer Faces interface within a JSP page.
The figure below shows how JSF fits into a web application's architecture.
One of JSF's prominent features is the ability to wire client-generated events to server-side event handlers. For example, when a user clicks a link or a button, methods in a class can be called. These methods can be listeners or actions. Listeners typically alter the state of a page-backing Java class or managed bean. They can alter the JSF life cycle, but they do not typically control navigation. Actions are no-argument methods that return a string that signifies where to go next. Returning null from an action means, "Stay on the same page."
AppFuse uses Facelets
for its view implementation. It used to use JSP, but we found many problems with JSF + JSP - many of them documented in Hans Bergsten's Improving JSF by Dumping JSP
. If you'd like to learn more about Facelets, see Rick Hightower's Facelets fits JSF like a glove
.
 | Learning JSF If you want a more in-depth learning experience, I suggest you read David Geary's Core JSF . I had it close by my side and used it frequently while integrating JSF into AppFuse. Thanks for the help David and Cay (co-author)! |
Create a PersonListTest
Testing is an important part of any application, and testing a JSF application isn't too difficult. In most web frameworks, an "Action" of some sort contains the controller logic. However, with JSF, they're commonly referred to as "Managed Beans". The methods within these beans are called actions. This tutorial is not going to teach you a whole lot about how JSF works, but it will get you up and running quickly with it.
Managed Beans in JSF follow a command pattern
similar to the one used by Struts 2 Actions. Since these classes are simple POJOs and don't depend on the Servlet API at all, they're very easy to test. AppFuse does most of the hard work for you, meaning it initializes the FacesContext and allows you to grab pre-configured managed beans. Shale
has a testing framework
that AppFuse may eventually adopt. If you're interested in helping do the integration, please let us know on the dev mailing list
.
Create a PersonListTest.java class in src/test/java/**/webapp/action:
package org.appfuse.tutorial.webapp.action;
import org.appfuse.webapp.action.BasePageTestCase;
import org.appfuse.service.GenericManager;
import org.appfuse.tutorial.model.Person;
public class PersonListTest extends BasePageTestCase {
private PersonList bean;
private GenericManager<Person, Long> personManager;
public void setPersonManager(GenericManager<Person, Long> personManager) {
this.personManager = personManager;
}
@Override
@SuppressWarnings("unchecked")
protected void onSetUp() throws Exception {
super.onSetUp();
bean = new PersonList();
bean.setPersonManager(personManager);
Person person = new Person();
person.setFirstName("Abbie Loo");
person.setLastName("Raible");
personManager.save(person);
}
@Override
protected void onTearDown() throws Exception {
super.onTearDown();
bean = null;
}
public void testSearch() throws Exception {
assertTrue(bean.getPersons().size() >= 1);
assertFalse(bean.hasErrors());
}
}
This class will not compile until you create the PersonList class.
Create a PersonList class that will fetch people
Create a PersonList.java file in src/main/java/**/webapp/action:
package org.appfuse.tutorial.webapp.action;
import java.io.Serializable;
import java.util.List;
import org.appfuse.webapp.action.BasePage;
import org.appfuse.service.GenericManager;
public class PersonList extends BasePage implements Serializable {
private GenericManager personManager;
public void setPersonManager(GenericManager manager) {
this.personManager = manager;
}
public PersonList() {
setSortColumn("id"); }
public List getPersons() {
return sort(personManager.getAll());
}
}
The sort() method (called in the getPersons() method) is in the BasePage class. This method makes it possible for you to sort columns in the UI.
Now you need to add the managed-bean definition for the PersonList class. Open src/main/webapp/WEB-INF/faces-config.xml and add the following managed-bean definition (towards the bottom of the file):
<managed-bean>
<managed-bean-name>personList</managed-bean-name>
<managed-bean-class>org.appfuse.tutorial.webapp.action.PersonList</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>personManager</property-name>
<value>#{personManager}</value>
</managed-property>
</managed-bean>
If you run mvn test -Dtest=PersonListTest, your test should pass.
Nice!
BUILD SUCCESSFUL
Total time: 16 seconds
Create persons.xhtml to show search results
Create a src/main/webapp/persons.xhtml page to display the list of people:
<html xmlns="http: xmlns:c="http://java.sun.com/jstl/core"
xmlns:f="http: xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http: xmlns:t="http://myfaces.apache.org/tomahawk">
<f:view>
<f:loadBundle var="text" basename="#{personList.bundleName}"/>
<head>
<title>#{text['personList.title']}</title>
<meta name="heading" content="#{text['personList.heading']}"/>
<meta name="menu" content="PersonMenu"/>
</head>
<body id="personList">
<h:form id="editPerson">
<h:commandButton value="#{text['button.add']}" action="add" id="add" immediate="true" styleClass="button"/>
<h:commandButton value="#{text['button.done']}" action="mainMenu" id="cancel" immediate="true" styleClass="button" style="margin-left: 5px"/>
<!-- Error from this table is caused by http:<t:dataTable id="persons" var="person" style="margin-top: 10px"
value="#{personList.persons}" rows="25" sortColumn="#{personList.sortColumn}"
sortAscending="#{personList.ascending}" styleClass="scrollerTable table"
headerClass="standardTable_Header" rowClasses="standardTable_Row1,standardTable_Row2"
columnClasses="standardTable_Column,standardTable_Column,standardTable_Column,standardTable_Column,standardTable_ColumnCentered">
<t:column>
<f:facet name="header">
<t:commandSortHeader columnName="id" arrow="true">
<h:outputText value="#{text['person.id']}" />
</t:commandSortHeader>
</f:facet>
<h:commandLink action="#{personForm.edit}" value="#{person.id}">
<f:param name="id" value="#{person.id}"/>
<f:param name="from" value="list"/>
</h:commandLink>
</t:column>
<t:column>
<f:facet name="header">
<t:commandSortHeader columnName="firstName" arrow="true">
<h:outputText value="#{text['person.firstName']}" />
</t:commandSortHeader>
</f:facet>
<h:outputText value="#{person.firstName}" escape="true"/>
</t:column>
<t:column>
<f:facet name="header">
<t:commandSortHeader columnName="lastName" arrow="true">
<h:outputText value="#{text['person.lastName']}" />
</t:commandSortHeader>
</f:facet>
<h:outputText value="#{person.lastName}" escape="true"/>
</t:column>
</t:dataTable>
<ui:include src="/common/tableFooter.xhtml">
<ui:param name="tableName" value="persons"/>
</ui:include>
<script type="text/javascript">
highlightTableRows("editPerson:persons");
</script>
</h:form>
</body>
</f:view>
</html>
Add a navigation-rule in src/main/webapp/WEB-INF/faces-config.xml that routes clicking on the Add button to the personForm.xhtml you'll create in step 5.
<navigation-rule>
<from-view-id>/persons.xhtml</from-view-id>
<navigation-case>
<from-outcome>add</from-outcome>
<to-view-id>/personForm.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
Open src/main/resources/ApplicationResources.properties and add i18n keys/values for the various "person" properties:
# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name
person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.
# -- person list page --
personList.title=Person List
personList.heading=Persons
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
Run mvn jetty:run-war and open http://localhost:8080/persons.html
in your browser. Login with admin/admin and you should see a screen similar to the figure below.
Security settings for AppFuse specify that all *.html url-patterns should be protected (except for /signup.html and /passwordHint.html). This guarantees that clients must go through JSF's FacesServlet to get to view pages.
 | CSS Customization 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/action:
package org.appfuse.tutorial.webapp.action;
import org.appfuse.service.GenericManager;
import org.appfuse.tutorial.model.Person;
import org.appfuse.webapp.action.BasePageTestCase;
public class PersonFormTest extends BasePageTestCase {
private PersonForm bean;
private GenericManager<Person, Long> personManager;
public void setPersonManager(GenericManager<Person, Long> personManager) {
this.personManager = personManager;
}
@Override
protected void onSetUp() throws Exception {
super.onSetUp();
bean = new PersonForm();
bean.setPersonManager(personManager);
}
@Override
protected void onTearDown() throws Exception {
super.onTearDown();
bean = null;
}
public void testAdd() throws Exception {
Person person = new Person();
person.setFirstName("firstName");
person.setLastName("lastName");
bean.setPerson(person);
assertEquals("list", bean.save());
assertFalse(bean.hasErrors());
}
public void testEdit() throws Exception {
log.debug("testing edit...");
bean.setId(1L);
assertEquals("edit", bean.edit());
assertNotNull(bean.getPerson());
assertFalse(bean.hasErrors());
}
public void testSave() {
bean.setId(1L);
assertEquals("edit", bean.edit());
assertNotNull(bean.getPerson());
Person person = bean.getPerson();
person.setFirstName("firstName");
person.setLastName("lastName");
bean.setPerson(person);
assertEquals("edit", bean.save());
assertFalse(bean.hasErrors());
}
public void testRemove() throws Exception {
Person person = new Person();
person.setId(2L);
bean.setPerson(person);
assertEquals("list", bean.delete());
assertFalse(bean.hasErrors());
}
}
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/action, create a PersonForm.java class that extends AppFuse's BasePage
. Populate it with the following code:
package org.appfuse.tutorial.webapp.action;
import java.io.Serializable;
import org.appfuse.tutorial.model.Person;
import org.appfuse.webapp.action.BasePage;
import org.appfuse.service.GenericManager;
public class PersonForm extends BasePage implements Serializable {
private GenericManager<Person, Long> personManager;
private Person person = new Person();
private Long id;
public void setPersonManager(GenericManager<Person, Long> manager) {
this.personManager = manager;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public void setId(Long id) {
this.id = id;
}
public String delete() {
personManager.remove(person.getId());
addMessage("person.deleted");
return "list";
}
public String edit() {
if (id != null) {
person = personManager.get(id);
} else {
person = new Person();
}
return "edit";
}
public String save() {
boolean isNew = (person.getId() == null);
personManager.save(person);
String key = (isNew) ? "person.added" : "person.updated";
addMessage(key);
if (isNew) {
return "list";
} else {
return "edit";
}
}
}
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 want to customize these messages, to add a person's name or something, simply add a {0} placeholder in the key's message and then use the addMessage(key, stringtoreplace) method.
Now you need to tell JSF that this bean exists. Add a managed-bean definition for personForm to src/main/webapp/WEB-INF/faces-config.xml:
<managed-bean>
<managed-bean-name>personForm</managed-bean-name>
<managed-bean-class>org.appfuse.tutorial.webapp.action.PersonForm</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>id</property-name>
<value>#{param.id}</value>
</managed-property>
<managed-property>
<property-name>personManager</property-name>
<value>#{personManager}</value>
</managed-property>
</managed-bean>
 | Spring's DelegatingVariableResolver allows JSF to resolve #{personManager} to the personManager Spring bean. DelegatingVariableResolver has already been defined as a variableResolver at the top of your faces-config.xml file. |
Near the top of faces-config.xml, add navigation-rules that controls page flow after actions are executed:
<navigation-rule>
<from-view-id>/personForm.xhtml</from-view-id>
<navigation-case>
<from-outcome>cancel</from-outcome>
<to-view-id>/persons.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>list</from-outcome>
<to-view-id>/persons.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
You'll also need to modify the navigation-rule you added for persons.xhtml so an "edit" result routes to the personForm.xhtml as well. The first part of the following XML should already exist in faces-config.xml.
<navigation-rule>
<from-view-id>/persons.xhtml</from-view-id>
<navigation-case>
<from-outcome>add</from-outcome>
<to-view-id>/personForm.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>edit</from-outcome>
<to-view-id>/personForm.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
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.
<table name='person'>
<column>id</column>
<column>first_name</column>
<column>last_name</column>
<row>
<value>1</value>
<value>Matt</value>
<value>Raible</value>
</row>
<row>
<value>2</value>
<value>Bob</value>
<value>Johnson</value>
</row>
</table>
DbUnit loads this file before you run any tests, so these records will be available to your PersonFormTest class. Save all your files and run the tests in PersonFormTest using the command mvn test -Dtest=PersonFormTest.
BUILD SUCCESSFUL
Total time: 16 seconds
Create personForm.xhtml to edit a person
Create a src/main/webapp/personForm.xhtml page to display the form:
<html xmlns="http: xmlns:c="http://java.sun.com/jstl/core"
xmlns:f="http: xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http: xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:v="http:>
<f:view>
<f:loadBundle var="text" basename="#{personForm.bundleName}"/>
<head>
<title>#{text['personDetail.title']}</title>
<meta name="heading" content="#{text['personDetail.heading']}"/>
<meta name="menu" content="PersonMenu"/>
</head>
<body id="personForm">
<h:form id="personForm">
<h:inputHidden value="#{personForm.person.id}" id="id"/>
<h:panelGrid columns="3">
<h:outputLabel styleClass="desc" for="firstName" value="#{text['person.firstName']}"/>
<h:inputText styleClass="text medium" id="firstName" value="#{personForm.person.firstName}"/>
<t:message for="firstName" styleClass="fieldError"/>
<h:outputLabel styleClass="desc" for="lastName" value="#{text['person.lastName']}"/>
<h:inputText styleClass="text medium" id="lastName" value="#{personForm.person.lastName}"/>
<t:message for="lastName" styleClass="fieldError"/>
<h:panelGroup styleClass="buttonBar bottom">
<h:commandButton value="#{text['button.save']}" action="#{personForm.save}" id="save" styleClass="button"/>
<c:if test="${not empty personForm.person.id}">
<h:commandButton value="#{text['button.delete']}" action="#{personForm.delete}"
id="delete" styleClass="button"/>
</c:if>
<h:commandButton value="#{text['button.cancel']}" action="cancel" immediate="true"
id="cancel" styleClass="button"/>
</h:panelGroup>
<h:outputText/><h:outputText/>
</h:panelGrid>
</h:form>
<script type="text/javascript">
Form.focusFirstElement($('personForm'));
highlightFormElements();
</script>
</body>
</f:view>
</html>
JSF reduces the amount of HTML you have to write for a form. Normally, JSF's <h:panelGrid> handles rendering an HTML <table> for you. However, since AppFuse uses Wufoo's (much prettier) form layout, we've modified this tag so it renders <ul> and <li> tags instead.
Run mvn jetty:run-war, open your browser to http://localhost:8080/persons.html
, 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.
 | Displaying success messages The src/main/webapp/common/messages.jsp file in AppFuse renders the success message in this screen. This file is included in decorators/default.jsp. It also handles displaying validation errors:
|
Configure Validation
Implementing validation with JSF is quite simple. For server-side validation, all you need to do is add required="true" to your <h:inputText> tags. For client-side validation, you need to add a nested <v:commonsValidator> tag. Other validations (besides required) can be specified by nested validation tags in your inputText tags.
In the personForm.xhtml you created, there is no validation-related information. Therefore, perform the following steps to make firstName and lastName required fields.
- Add required="true" to the inputText fields in personForm.xhtml. This makes these fields required on the server-side.
- Add a <v:commonsValidator> tag to these fields to enable client-side validation (code below).
- Add a <v:validatorScript> tag after your form to print out the JavaScript functions for client-side validation.
- Add an onSubmit() event handler to the <h:form> tag.
- Add onclick() event handlers to the Cancel and Delete buttons to disable validation.
Below is the revised contents of the <h:form> tag with these changes. Replace the <h:form> in your personForm.xhtml with these changes.
<h:form id="personForm" onsubmit="return validatePersonForm(this)">
<h:inputHidden value="#{personForm.person.id}" id="id"/>
<h:panelGrid columns="3">
<h:outputLabel styleClass="desc" for="firstName" value="#{text['person.firstName']}"/>
<h:inputText styleClass="text medium" id="firstName" value="#{personForm.person.firstName}" required="true">
<v:commonsValidator type="required" arg="#{text['person.firstName']}"/>
</h:inputText>
<t:message for="firstName" styleClass="fieldError"/>
<h:outputLabel styleClass="desc" for="lastName" value="#{text['person.lastName']}"/>
<h:inputText styleClass="text medium" id="lastName" value="#{personForm.person.lastName}" required="true">
<v:commonsValidator type="required" arg="#{text['person.lastName']}"/>
</h:inputText>
<t:message for="lastName" styleClass="fieldError"/>
<h:panelGroup styleClass="buttonBar bottom">
<h:commandButton value="#{text['button.save']}" action="#{personForm.save}" id="save" styleClass="button"/>
<c:if test="${not empty personForm.person.id}">
<h:commandButton value="#{text['button.delete']}" action="#{personForm.delete}"
id="delete" styleClass="button" onclick="bCancel=true; return confirmDelete('Person')"/>
</c:if>
<h:commandButton value="#{text['button.cancel']}" action="cancel" immediate="true"
id="cancel" styleClass="button" onclick="bCancel=true"/>
</h:panelGroup>
<h:outputText/><h:outputText/>
</h:panelGrid>
</h:form>
AppFuse contains a custom LabelRenderer
for the <h:outputLabel> tag. This render adds asterisks for required fields.
There are a number of different validators available for JSF from the MyFaces project. However, I can't seem to locate a link to them at this time. This example only shows a way to make Strings required. The userForm.xhtml
contains examples of validating e-mail addressing and validating with regular expressions.
After saving all your files and running mvn jetty:run-war, validation should kick in when you try to save this form. To test, go to http://localhost:8080/personForm.html
and try to add a new user with no first or last name. You should get the following JavaScript alert:
 | JSF 1.1 doesn't allow injecting the field label into the error message for server-side validation errors. For more information see this mailing list thread . |
To make sure things are really working as expected, you can turn off JavaScript and ensure the server-side validation is working. This is easy in Firefox
(my favorite browser), just go to Tools → Options → Web Features and uncheck "Enable JavaScript". Now if you clear the fields and save the form, you should see the following:
Create a Canoo WebTest to see 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 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.
<!-- runs person-related tests -->
<target name="PersonTests"
depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
description="Call and executes all person test cases (targets)">
<echo>Successfully ran all Person UI tests!</echo>
</target>
<!-- Verify the people list screen displays without errors -->
<target name="SearchPeople" description="Tests search for and displaying all people">
<webtest name="searchPeople">
&config;
<steps>
&login;
<invoke description="View Persons List" url="/persons.html"/>
<verifytitle description="we should see the personList title"
text=".*${personList.title}.*" regex="true"/>
</steps>
</webtest>
</target>
<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson" description="Tests editing an existing Person's information">
<webtest name="editPerson">
&config;
<steps>
&login;
<invoke description="View Persons List" url="/persons.html"/>
<clicklink label="1"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
</steps>
</webtest>
</target>
<!-- Edit a person and then save -->
<target name="SavePerson"
description="Tests editing and saving a user">
<webtest name="savePerson">
&config;
<steps>
&login;
<invoke description="View Persons List" url="/persons.html"/>
<clicklink label="1"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
<!-- update some of the required fields -->
<setinputfield description="set firstName" name="personForm:firstName" value="Canoo"/>
<setinputfield description="set lastName" name="personForm:lastName" value="WebTest"/>
<clickbutton label="${button.save}" description="Click Save"/>
<verifytitle description="Page re-appears if save successful"
text=".*${personDetail.title}.*" regex="true"/>
<verifytext description="verify success message" text="${person.updated}"/>
</steps>
</webtest>
</target>
<!-- Add a new Person -->
<target name="AddPerson"
description="Adds a new Person">
<webtest name="addPerson">
&config;
<steps>
&login;
<invoke description="View Person Form" url="/personForm.html"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
<!-- enter required fields -->
<setinputfield description="set firstName" name="personForm:firstName" value="Jack"/>
<setinputfield description="set lastName" name="personForm:lastName" value="Raible"/>
<clickbutton label="${button.save}" description="Click button 'Save'"/>
<verifytitle description="Person List appears if save successful"
text=".*${personList.title}.*" regex="true"/>
<verifytext description="verify success message" text="${person.added}"/>
</steps>
</webtest>
</target>
<!-- Delete existing person -->
<target name="DeletePerson"
description="Deletes existing Person">
<webtest name="deletePerson">
&config;
<steps>
&login;
<invoke description="View Persons List" url="/persons.html"/>
<clicklink label="2"/>
<prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
<clickbutton label="${button.delete}" description="Click button 'Delete'"/>
<verifyNoDialogResponses/>
<verifytitle description="display Person List" text=".*${personList.title}.*" regex="true"/>
<verifytext description="verify success message" text="${person.deleted}"/>
</steps>
</webtest>
</target>
After adding this, you should be able to run mvn integration-test -Dtest=PersonTests and have these tests execute. If this command results in "BUILD SUCCESSFUL" - nice work!
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.
<target name="run-all-tests"
depends="Login,Logout,PasswordHint,Signup,UserTests,FlushCache,FileUpload,PersonTests"
description="Call and executes all test cases (targets)"/>
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/WEB-INF/pages/mainMenu.jsp:
<li>
<a href="persons.html">#{text['menu.viewPeople']}</a>
</li>
Where menu.viewPeople is an entry in src/main/resources/ApplicationResources.properties.
menu.viewPeople=View People
 | Modifying AppFuse core files You can run war:inplace to get mainMenu.jsp file in your project. You'll want to check your project into source control before you do this so you can delete files you don't modify. |
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/webapp/WEB-INF/menu-config.xml:
<Menu name="PeopleMenu" title="menu.viewPeople" page="/persons.html"/>
Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then create src/main/webapp/common/menu.jsp and add the following code to it.
<%@ include file="/common/taglibs.jsp"%>
<menu:useMenuDisplayer name="Velocity" config="WEB-INF/classes/cssHorizontalMenu.vm" permissions="rolesAdapter">
<ul id="primary-nav" class="menuList">
<li class="pad"> </li>
<c:if test="${empty pageContext.request.remoteUser}">
<li><a href="<c:url value="/login.jsp"/>" class="current">
<fmt:message key="login.title"/></a></li>
</c:if>
<menu:displayMenu name="MainMenu"/>
<menu:displayMenu name="UserMenu"/>
<menu:displayMenu name="PeopleMenu"/>
<menu:displayMenu name="AdminMenu"/>
<menu:displayMenu name="Logout"/>
</ul>
</menu:useMenuDisplayer>
Now if you run mvn jetty:run-war and go to http://localhost:8080/mainMenu.html
, you should see something like the screenshot below.
Notice that there is a new link in your main screen (from mainMenu.jsp) and on the top in your menu bar (from menu.jsp).
That's it!
You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and JSF - Congratulations! Now the real test is if you can run all the tests in your app without failure. To test, run mvn integration-test. This will run all the unit and integration tests within your project.
Happy Day!
BUILD SUCCESSFUL
Total time: 1 minute 41 seconds