Raible's Wiki

Raible Designs
Wiki Home
News
Recent Changes

AppFuse

Homepage
  - Korean
  - Chinese
  - Italian
  - Japanese

QuickStart Guide
  - Chinese
  - French
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish
  - Japanese

User Guide
  - Korean
  - Chinese

Tutorials
  - Chinese
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish

FAQ
  - Korean

Latest Downloads

Other Applications

Struts Resume
Security Example
Struts Menu

Set your name in
UserPreferences


Referenced by
Articles
Articles_cn
Articles_de
Articles_pt
ValidationAndListWeb...
WebWorkActions




JSPWiki v2.2.33

[RSS]


Hide Menu

ValidationAndListWebWork


This is version 1. It is not the current version, and thus it cannot be edited.
[Back to current version]   [Restore this version]


Part IV: Adding Validation and List Screen - Adding validation logic to the Person object so that firstName and lastName are required fields and adding a list screen to display all person records in the database.
This tutorial depends on Part III: Creating WebWork Actions and JSPs.

About this Tutorial

This tutorial will show you how to add Validation logic to the Person object using WebWork's Validation framework. You'll also create a list screen using the Display Tag Library to display all the people in the database.
I will tell you how I do stuff in the Real World in text like this.

Table of Contents

  • [1] Create a Person-validation.xml file with validation rules
  • [2] View JSP with validation added and test
  • [3] Add testGetPeople methods to DAO and Manager Tests
  • [4] Add getPeople methods to PersonDao and Manager
  • [5] Add testSearch methods to Action Test
  • [6] Add search method to Action
  • [7] Create personList.jsp and Canoo test
  • [8] Add link to menu

Create a Person-validation.xml file with validation rules [#1]

To implement validation in WebWork's validation framework, there are a couple things you need to do. The first is create a validation.xml file, the second is add a validation interceptor reference to the action you want to validate. More detailed information on WebWork's validation can be found in WebWork's Validation documentation.

WebWork allows two types of validation - per-action and model-based. Since you likely want the same rules applied for the person object across different actions, this tutorial will use model-based.

Create a new file named Person-validation.xml in the src/dao/**/model directory. It should contain the following XML:


<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN" 
  "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
<validators>
    <field name="person.firstName">
        <field-validator type="requiredstring">
            <message key="errors.required"/>
        </field-validator>
    </field>
    <field name="person.lastName">
        <field-validator type="requiredstring">
            <message key="errors.required"/>
        </field-validator>
    </field>
</validators>

The "errors.message" key in ApplicationResources_*.properties (listed below) will use the field's "name" attribute to do internationalization. You can also give the <message> element a body if you don't need i18n.


errors.required=${getText(fieldName)} is a required field.

Now you need to configure PersonAction to know about "http://wiki.opensymphony.com/display/WW/Visitor+validation". To do this, create a PersonAction-validation.xml file in the same directory as PersonAction. Fill it with the following XML:


<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN"     
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
<validators>
    <field name="person">
        <field-validator type="visitor">
            <param name="appendPrefix">false</param>
            <message/>
        </field-validator>
    </field>
</validators>

Unfortunately, WebWork doesn't have a transparent mechanism for reading from the Person-validation.xml file and marking fields as required on the UI. AppFuse's Struts and Spring versions use a LabelTag that makes this possible, but they also both use Commons Validator. It is my hope to someday provide this same functionality for WebWork. In the meantime, the JSP tags "required" attribute has nothing to with the validation rules you specify. Rather, they simply add an asterisk to the label with no further functionality.

NOTE: Client-side validation of model-based validation rules doesn't work with the SpringObjectFactory that AppFuse uses. Furthermore, I believe that WebWork's client-side validation needs some additional features, namely: allow cancelling and showing all errors in one dialog. Because of this, only server-side validation works in WebWork+AppFuse. If you'd like, you can read more about my frustrations with client-side validation.

As a workaround, you can use per-action validation. Just copy the Person-validation.xml file to the "webapp.action" package and rename it to PersonAction-validation.xml.

To enable validation for the "savePerson" action, you'll need to uncomment the "validator" property we commented out earlier. Make sure the "savePerson" <action> in web/WEB-INF/classes/xwork.xml has the following XML:


<interceptor-ref name="validationStack"/>

As an FYI, the validationStack used in AppFuse is a bit different from the default one that WebWork ships with. More information can be found in WebWork's JIRA.

View JSP with validation added and test [#2]

Now that you have Validation configured for this object, whenever an action has the "validationStack" interceptor, these rules will be applied. After saving all your files and deploying, validation should kick in when you try to save this form. To test, run ant db-load deploy, start Tomcat and go to http://localhost:8080/appfuse/editPerson.html?id=1.

If you erase the values in the firstName and lastName fields and click the save button, you should see the following:

validation-required-nojs.png

Add testGetPeople methods to DAO and Manager Tests [#3]

To create a List screen (also called a master screen), we need to create methods that will return all the rows from our person table. Let's start by adding tests for these methods to our PersonDaoTest and PersonManagerTest classes. I usually name this method getEntities (i.e. getUsers), but you could also use getAll or search - it's really just a matter of personal preference.

Open test/dao/**/dao/PersonDaoTest.java and add a testGetPeople method:


    public void testGetPeople() {
        person = new Person();
        List results = dao.getPeople(person);
        assertTrue(results.size() 0);
    }

The reason I'm passing in a person object to the getPeople method is to allow for filtering (based on values in person) in the future. Adding this parameter in your getPeople() method signature is optional, but the rest of this tutorial assumes you have done this.

Now open test/service/**/service/PersonManagerTest.java and add a testGetPeople method:


    public void testGetPeople() {
        List results = mgr.getPeople(new Person());
        assertTrue(results.size() 0);
    }

In order for these tests to compile, you need to add the getPeople() method to the PersonDao and PersonManager interfaces, and their implementations.

Add getPeople() method to DAO and Manager [#4]

Open src/dao/**/dao/PersonDao.java and add the getPeople() method signature:


    public List getPeople(Person person);

Now add the same method signature to src/service/**/service/PersonManager.java. Save all your files and adjust the imports in your tests. Next you need to implement the getPeople() method in your implementation classes. Open src/dao/**/dao/hibernate/PersonDaoHibernate.java and add the following method:


    public List getPeople(Person person) {
        return getHibernateTemplate().find("from Person");
    }

You'll notice here that nothing is being done with the person parameter. This is just a placeholder for now - in the future you may want to filter on it's properties using Hibernate's Query Language (HQL) or using Criteria Queries.

An example using a Criteria Query:


    Example example = Example.create(person)
                             .excludeZeroes()    // exclude zero valued properties
                             .ignoreCase();      // perform case insensitive string comparisons
    try {
        return getSession().createCriteria(Person.class)
                           .add(example)
                           .list();
    catch (Exception e) {
        throw new DataAccessException(e.getMessage());
    }
    return new ArrayList();

Now implement the getPeople() method in src/service/**/impl/PersonManagerImpl.java:


    public List getPeople(Person person) {
        return dao.getPeople(person);
    }

After saving all your changes, you should be able to run both tests by executing the following:

  • ant test-dao -Dtestcase=PersonDao
  • ant test-service -Dtestcase=PersonManager

If everything works - nice job! Now you need to add this retrieve all functionality to the web tier.

Add testSearch() method to Action Test [#5]

Open test/web/**/action/PersonActionTest.java and add the following method:


    public void testSearch() throws Exception {
        assertNull(action.getPeople());
        assertEquals(action.list()"success");
        assertNotNull(action.getPeople());
        assertFalse(action.hasActionErrors());
    }

This class will not compile since the getPeople() and list() methods doesn't yet exist in PersonAction.

Add list() and getPeople() method to Action [#6]

Open src/web/**/action/PersonAction.java and add the list() method to the body of the PersonAction class. While you're at it, add a "people" class variable and a getPeople() method to expose it to the UI.
I used UserAction.search() as a template for this method.


    private List people;

    public List getPeople() {
        return people;
    }
    
    public String list() {
        people = personManager.getPeople(new Person());

        return SUCCESS;
    }

You can test this works by running ant test-web -Dtestcase=PersonAction.

Nice! BUILD SUCCESSFUL
Total time: 10 seconds

Create personList.jsp and Canoo test [#7]

There should already be a personList.jsp file in web/pages. If not, you can create it using viewgen. From the command-line, navigate to extras/viewgen and run ant -Dform.name=Person. This will generate a personList.jsp in extras/viewgen/build.

Once personList.jsp exists in web/pages, open it for editing.

At the top of the file is a <ww:set> tags that tries to expose the "people" from PersonAction. However, by default, the generated name is just the plural of the object name. So you'll need to change the referenced value from "persons" to "people".


<ww:set name="personList" value="people" scope="request"/>

The template you used to create this JSP has the column for the id property hard-coded, so XDoclet adds it twice. To remove this from personList.jsp, delete the following from this file:


    <display:column property="id" sort="true" headerClass="sortable"
        titleKey="personForm.id"/>

If anyone knows of a way to modify the extras/viewgen/src/List_jsp.xdt to not include this column tag, please let me know.

Another thing you'll probably want to change is the displayed plural form of the items you're listing. The generated name in this example is "persons" and it should be "people". At or near line 30, you should have the following line:

<display:setProperty name="paging.banner.items_name" value="persons"/>

Change it to:

<display:setProperty name="paging.banner.items_name" value="people"/>

Finally, add the title and heading keys (personList.title and personList.heading) to web/WEB-INF/classes/ApplicationResources_en.properties. Open this file and add the following:

# -- person list page --
personList.title=Person List
personList.heading=All People

As a reminder, the personList.title is what ends up in the brower's title bar (the <title> tag) and personList.heading will be put into an <h1> tag before any page content.

To expose the list() method as a URL, add a new "people" action to web/WEB-INF/classes/xwork.xml:


    <action name="people" class="personAction" method="list"
        <result name="success">/WEB-INF/pages/personList.jsp</result> 
    </action>

At this point, you should be able to run ant clean deploy, start Tomcat and view this page in your browser at http://localhost:8080/appfuse/people.html.

Now that you have a List Screen, let's change the pages that are displayed after adding and deleting a new Person. In web/WEB-INF/classes/xwork.xml, change the savePersons's "input" and "success" results to be "people.html".

You will also need to change the Canoo tests "AddPerson" and "DeletePerson". Open test/web/web-tests.xml and change the following line in the "AddPerson" target:

<verifytitle description="Main Menu appears if save successful" 
    text=".*${mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="Person List appears if save successful" 
    text=".*${personList.title}.*" regex="true"/>

Then in the "DeletePerson" target, change the following line:

<verifytitle description="display Main Menu" 
    text=".*$(mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="display Person List" text=".*${personList.title}.*" regex="true"/>

To test that displaying this page works, create a new JSP test in test/web/web-tests.xml:


    <!-- 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="click View People link" url="/people.html"/>
                <verifytitle description="we should see the personList title" 
                    text=".*${personList.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

You'll also want to add the "SearchPeople" target to the "PersonTests" target so it will be executed along with all the other person-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 JSP tests!</echo>
    </target>

Now you can run ant test-canoo -Dtestcase=SearchPeople (or ant test-jsp if Tomcat isn't running) and hopefully it will result in "BUILD SUCCESSFUL". If so - nice work!

Add link to menu [#8]

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 web/pages/mainMenu.jsp:


    <li>
        <a href="<c:url value="/people.html"/>"><fmt:message key="menu.viewPeople"/></a>
    </li>

Where menu.viewPeople is an entry in web/WEB-INF/classes/ApplicationResources_en.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 web/WEB-INF/menu-config.xml:


<Menu name="PeopleMenu" title="menu.viewPeople" forward="viewPeople"/>

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add this new menu to web/common/menu.jsp - which should now look as follows:


<%@ include file="/common/taglibs.jsp"%>

<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PeopleMenu"/>
    <menu:displayMenu name="FileUpload"/>
    <menu:displayMenu name="FlushCache"/>
    <menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>

Now if you run ant clean deploy start Tomcat and go to http://localhost:8080/appfuse/mainMenu.html, you should see something like the screenshot below.

new-menu-item.png

Notice that there is a new link on the left side (from mainMenu.jsp) and on the right in our menu (from menu.jsp).

That's it!

You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and WebWork - Congratulations! Now the real test is if you can run all the tests in your app without failure. To test, stop tomcat and run ant clean test-all. This will run all the unit tests within your project. As a reminder, it should be easy to setup and test AppFuse from scratch using ant setup-db setup-tomcat test-all. Also, if you're looking for more robust examples - checkout Struts Resume.

Happy Day!

BUILD SUCCESSFUL
Total time: 2 minutes 31 seconds

If you'd like, you can download the files created in this tutorial.



Go to top   More info...   Attach file...
This particular version was published on 06-Nov-2006 13:52:39 MST by MattRaible.