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

Edit this page


Referenced by
Articles
Articles_pt
CreateActions
CreateActions_de
SpringControllers_ko
ValidationAndList_pt




JSPWiki v2.2.33

[RSS]


Hide Menu

ValidationAndList


Part IV: Adding Validation and List Screen - Adding validation logic to the PersonForm 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 Actions and JSPs.

About this Tutorial

This tutorial will show you how to add Validation logic (client and server-side) to the PersonForm object using Struts' Validator. We'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] Add XDoclet Validator tags to Person.java
  • [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

Add XDoclet Validator tags to Person.java [#1]

To use the Struts Validator, normally you have to write a validation.xml file by hand. If you're not using AppFuse, you also have to configure the Validator Plugin and error keys in your ApplicationResources.properties. For more information on this, see the Validation Made Easy Tutorial (there's also a rich set of tutorials for Struts itself).

Thanks to XDoclet, it's much easier - you just need to add a couple of @struts.validator tags to the Person class. Open it up (src/dao/**/model/Person.java) and modify the getFirstName() and getLastName() methods to include @struts.validator type="required" tags.


    /**
     * @struts.validator type="required"
     * @hibernate.property column="first_name" length="50"
     */
    public String getFirstName() {
        return this.firstName;
    }

    /**
     * @struts.validator type="required" 
     * @hibernate.property column="last_name" length="50"
     */
    public String getLastName() {
        return this.lastName;
    }

You can also add a msgkey attribute to this tag to override the default message key for this error.


@struts.validator type="required" msgkey="errors.required"

The default key for type="required" is already errors.required, so I usually leave it to the default. This key is defined in web/WEB-INF/classes/ApplicationResources_*.properties. You'll notice that we put these tags on the getters of this class even though the XDoclet documentation says to put them on the setters. This is because we are generating our PersonForm.java - the template file (metadata/template/struts_form.xdt) takes care of putting these tags onto the setters in the generated file.

Now if you save Person.java and run ant clean webdoclet, a validation.xml file will be generated in build/appfuse/WEB-INF/. Its contents should have now have an entry for "personForm".


      <form name="personForm">
              <field property="firstName"
                     depends="required">

                  <arg0 key="personForm.firstName"/>
              </field>
              <field property="lastName"
                     depends="required">

                  <arg0 key="personForm.lastName"/>
              </field>
      </form>

Client-side validation is enabled by default in personForm.jsp. There is an <html:javascript> JSP tag and script at the bottom of this page that enables it. The following should already exist (thanks to AppGen) - but you might need to uncomment it if you commented it out in the last tutorial.

<html:javascript formName="personForm" cdata="false"
    dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
    src="<html:rewrite page="/scripts/validator.jsp"/>"></script>
NOTE: If you have nested objects with validation rules, those will be picked up and put into validation.xml. This is because an @struts.validator tag gets added to the setter of the nested object when the form is generated (using metadata/templates/struts_form.xdt). If you have many-to-many bi-directional relationships between objects, this can cause a problem. There are two solutions to fix this. The first is to remove the @struts.validator tag from struts_form.xdt and manually place it on the setter in your POJO. The second is described here.

View JSP with validation added and test [#2]

Now that you have Validation configured for this form, whenever this form is used in an action-mapping with validate="true", these rules will be applied. In the last tutorial, we added the "savePerson" action-mapping for PersonAction. The XDoclet tags for this action-mapping were:


 * @struts.action name="personForm" path="/savePerson" scope="request"
 *  validate="true" parameter="method" input="edit"

So now, as long as your web/pages/personForm.jsp has <html:form action="savePerson">, validation should kick in when we try to save this form. 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 get the following JavaScript alert.

validation-required.png

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:

validation-required-nojs.png

If you don't see these validation errors, there are a couple possibilities:

  • The form saves with a success message, but the firstName and lastName fields are now blank.
This is because the <html:form> in web/pages/personForm.jsp has action="editPerson" - make sure it has action="savePerson".
  • You click save, but a blank page appears.
The blank page indicates that the "input" attribute of you "savePerson" forward is incorrectly configured. Make sure it relates to a local or global action-forward. In this example, it should be input="edit", which points to the .personDetail tile's definition. From my experience, the input's value must be a forward, not a path to an action.
If you only want server-side validation (no JavaScript), you can remove the onsubmit attribute of <html:form> (in web/pages/personForm.jsp) as well as the Validator JavaScript tags at the bottom of the page.


<html:javascript formName="personForm" cdata="false"
      dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
      src="<html:rewrite page="/scripts/validator.jsp"/>"></script>

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() throws Exception {
        List results = new ArrayList();
        person = new Person();
        results.add(person);

        // set expected behavior on dao
        personDao.expects(once()).method("getPeople")
            .will(returnValue(results));

        List people = personManager.getPeople(null);
        assertTrue(people.size() == 1);
        personDao.verify();
    }

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:


    // filter on properties set in the person object
    HibernateCallback callback = new HibernateCallback() {
        public Object doInHibernate(Session sessionthrows HibernateException {
            Example ex = Example.create(person).ignoreCase().ignoreZeroes()
                                .enableLike(MatchMode.ANYWHERE);
            return session.createCriteria(Person.class).add(ex).list();
        }
    };
    return (ListgetHibernateTemplate().execute(callback);

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() {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Search");
        actionPerform();

        verifyForward("list");

        assertNotNull(getRequest().getAttribute(Constants.PERSON_LIST));
        verifyNoActionErrors();
    }

This class will not compile until you add the PERSON_LIST variable to the src/dao/**/Constants.java file.

I usually copy a similar variable that already exists in this file - i.e. USER_LIST.


    /**
     * The request scope attribute that holds the person list
     */
    public static final String PERSON_LIST = "personList";

Now save all your changes. You won't be able to run ant test-web -Dtestcase=PersonAction yet since PersonAction.search() does not exist (yet).

Add search method to Action [#6]

Open src/web/**/action/PersonAction.java and add the following XDoclet tag at the top - to forward to our list screen.


 * @struts.action-forward name="list" path="/WEB-INF/pages/personList.jsp"

Now add the search method to the body of the PersonAction class.

I used UserAction.search() as a template for this method.


    public ActionForward search(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
            throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'search' method");
        }

        PersonManager mgr = (PersonManagergetBean("personManager");
        List people = mgr.getPeople(null);
        request.setAttribute(Constants.PERSON_LIST, people);

        // return a forward to the person list definition
        return mapping.findForward("list");
    }

Run ant test-web -Dtestcase=PersonAction.

Nice! BUILD SUCCESSFUL
Total time: 1 minute 26 seconds

Create personList.jsp and Canoo test [#7]

Open the personList.jsp file in web/pages. You'll probably want to change the code to show the plural form of the items you're listing. The generated name in this example is "persons" and it should probably be people. At or near line 31, 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.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.

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/editPerson.html?method=Search.

Now that we have a List Screen, let's change the pages that are displayed after adding and deleting a new Person. In src/web/**/action/PersonAction.java, change the mapping.findForward("mainMenu") in the save, delete and cancel methods to be:


    return mapping.findForward("viewPeople");

You will also need to change verifyForward("mainMenu") to be verifyForward("viewPeople") in the testRemove method of test/web/**/action/PersonActionTest.java. Lastly, the Canoo tests "AddPerson" and "DeletePerson" need to be updated. 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"/>

Finally, declare the viewPeople forward in metadata/web/global-forwards.xml after viewUsers as below:

<forward name="viewPeople" path="/editPerson.html?method=Search"/>

The name "viewPeople" is used instead of "list" so that the search method will be executed, rather than simply forwarding to the personForm.jsp (which the "list" forward points to).

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="/editPerson.html?method=Search"/>
                <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:

NOTE: The other links in mainMenu.jsp don't use so this JSP can be shared among the various web framework implementations in AppFuse (i.e. Spring MVC and WebWork).


    <li>
        <html:link forward="viewPeople">
            <fmt:message key="menu.viewPeople"/>
        </html:link>
    </li>

Where menu.viewPeople is an entry in web/WEB-INF/classes/ApplicationResources.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 Struts - 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

Attachments:


Go to top   Edit this page   More info...   Attach file...
This page last changed on 07-Feb-2007 16:02:16 MST by MattRaible.