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

About this Tutorial

This tutorial creates master/detail screens with Java Server Faces (JSF). The JSF implementation used in AppFuse is Mojarra, but you should be able to use any 2.0+ JSF implementation. The list (master) screen will have the ability to sort columns, as well as page 25 records at a time. 

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-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. (wink)

Table of Contents

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

Source Code

Icon

The code for this tutorial is located in the "tutorial-jsf" 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-jsf

Introduction to JSF 

JavaServer Faces (JSF) is a component-based, event-driven web framework. 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."

Learning JSF

Icon

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 uses to simplify testing JSF beans even more.

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

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:

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 register this as a managed-bean with JSF. Spring's SpringBeanFacesELResolver (defined in faces-config.xml) makes this easy to do. Simple add the following annotations at the class-level:

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

Nice!

BUILD SUCCESS
Total time: 7.414s

Create persons.xhtml to show search results 

Create a src/main/webapp/persons.xhtml page to display the list of people:

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.

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/persons 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 JSF's FacesServlet 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/action:

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:

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.

Near the top of faces-config.xml, add navigation-rules that controls page flow after actions are executed:

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.

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. Save all your files and run the tests in PersonFormTest using the command mvn test -Dtest=PersonFormTest.

BUILD SUCCESS
Total time: 7.772s

Create personForm.xhtml to edit a person 

Create a src/main/webapp/personForm.xhtml page to display the form:

Run mvn jetty:run, open your browser to http://localhost:8080/persons, 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

Icon

The src/main/webapp/common/messages.jsp file in AppFuse renders the success message in this screen. This file is included in layouts/default.xhtml. It also handles displaying validation errors:

<%@ include file="/common/taglibs.jsp" %>
<c:if test="${not empty errors}">
    <div class="alert alert-danger alert-dismissable">
        <a href="#" data-dismiss="alert" class="close">&times;</a>
        <c:forEach var="error" items="${errors}">
            <c:out value="${error}"/><br />
        </c:forEach>
    </div>
    <c:remove var="errors"/>
</c:if>

<c:if test="${not empty messages}">
    <div class="alert alert-success alert-dismissable">
        <a href="#" data-dismiss="alert" class="close">&times;</a>
        <c:forEach var="msg" items="${messages}">
            <c:out value="${msg}"/><br />
        </c:forEach>
    </div>
    <c:remove var="messages" scope="session"/>
</c:if>

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. 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.

  1. Add required="true" to the inputText fields in personForm.xhtml. This makes these fields required on the server-side.
  2. Add a <p:message/> tag after the input field with a "for" attribute that matches the input field's id. 

Below is the revised contents of the <h:form> tag with these changes. Replace the <h:form> in your personForm.xhtml with these changes.

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:

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

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.xhtml:

Where menu.viewPeople is an entry in src/main/resources/ApplicationResources.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/webapp/WEB-INF/menu-config.xml:

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add the PersonMenu just before the AdminMenu in src/main/webapp/common/menu.jsp.

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.xhtml) 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!

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: 52.444s