At line 3 added 2 lines. |
;:''This tutorial depends on __Part II:__ [Creating new Managers|CreateManager].'' |
|
At line 4 changed 1 line. |
This tutorial will show you how to create an Action, a JUnit Test (using [StrutsTestCase|http://strutstestcase.sourceforge.net/]) , and a JSP for the form. The Action we create will talk to the PersonManager we created in the [Creating Managers|CreateManager] tutorial. This tutorial will simplify everything - we will not actually be rendering any data making the UI look pretty. The [next tutorial|ConfiguringTiles] will show you how to integrate your new JSP into your webapp. |
This tutorial will show you how to create a Struts Action, a JUnit Test (using [StrutsTestCase|http://strutstestcase.sourceforge.net/]), and a JSP for the form. The Action we create will talk to the PersonManager we created in the [Creating Managers|CreateManager] tutorial. |
At line 6 changed 1 line. |
;:%%(color: blue)''I will tell you how I do stuff in the __Real World__ in text like this.''%% |
By default, AppFuse ships with [Struts|http://struts.apache.org] as its web framework. As of 1.6+, you can use [Spring|http://www.springframework.org] or [WebWork|http://opensymphony.org/webwork] as your web framework. In 1.7, support was added for using [JSF|http://myfaces.apache.org] or [Tapestry|http://jakarta.apache.org/tapestry]. |
At line 8 changed 1 line. |
Let's get started by creating a new Action and JSP in AppFuse's architecture. |
To install any of these web frameworks instead of Struts, simply navigate to the ''extras'' directory and into the directory of the framework you want to install. The README.txt file in this directory has further instructions. The tutorials for these other frameworks are listed below. |
At line 12 added 9 lines. |
* __<span style="color: green">Spring:</span>__ [Creating Spring Controllers and JSPs|SpringControllers] |
* __<span style="color: blue">WebWork:</span>__ [Creating WebWork Actions and JSPs|WebWorkActions] |
* __<span style="color: purple">JSF:</span>__ [Creating JSF Beans and JSPs|JSFBeans] |
* __<span style="color: orange">Tapestry:</span>__ [Creating Tapestry Pages and Templates|TapestryPages] |
|
Let's get started by creating a new Struts Action and JSP your AppFuse project. |
|
;:%%(color: blue)''I will tell you how I do stuff in the __Real World__ in text like this.''%% |
|
At line 11 changed 4 lines. |
* [1] Create a skeleton JSP using XDoclet |
* [2] Create a new ActionTest to test our Action |
* [3] Create a new Action |
* [4] Display the JSP in a browser and run the ActionTest |
* [1] Add XDoclet Tags to Person to generate PersonForm |
* [2] Create skeleton JSPs using XDoclet |
* [3] Create PersonActionTest to test PersonAction |
* [4] Create PersonAction |
* [5] Run PersonActionTest |
* [6] Clean up the JSP to make it presentable |
* [7] Create Canoo WebTests to test browser-like actions |
At line 16 changed 2 lines. |
!!Create a skeleton JSP using XDoclet [#1] |
In this step, we'll generate a ''skeleton'' or our JSP for displaying information from the PersonForm. I say ''skeleton'' because it'll just be the <form> itself. It will contain table rows and Struts' <html:text> tags for each property in PersonForm.java. The tool that we use to do this was written by [Erik Hatcher|http://www.blogscene.org/erik/]. It's basically just a single class (FormTagsHandler.java) and a couple of XDoclet templates (FormKeys.xdt and StrutsForm_jsp.xdt). All these files are located in tools/strutsgen. |
!!Add XDoclet Tags to Person to generate PersonForm [#1] |
Now let's generate our PersonForm object for Struts and our web tier. To do this, we need to add XDoclet tags to the Person.java Object to create our Struts ActionForm. In the JavaDoc for the Person.java file, add the following @struts.form tag (use User.java if you need an example): |
At line 33 added 14 lines. |
[{Java2HtmlPlugin |
|
* @struts.form include-all="true" extends="BaseForm" |
}] |
|
;:''We extend org.appfuse.webapp.form.BaseForm because it has a toString() method that allows us to call log.debug(formName) to print out a reader-friendly view of the Form object.'' |
|
;:''If you haven't renamed the "org.appfuse" packages to "com.company" or otherwise don't have your model class in the default package, you may need to fully-qualify the reference to org.appfuse.webapp.form.BaseForm in the @struts.form tag.'' |
|
Now if you run __ant gen-forms__, Ant (and XDoclet) will generate a PersonForm.java for you in build/web/gen/**/form. |
|
!!Create skeleton JSPs using XDoclet [#2] |
In this step, you'll generate a JSP page to display information from the Person object. It will contain Struts' JSP tags that render table rows for each property in Person.java. The [AppGen] tool that's used to do this is based off a StrutsGen tool - which was originally written by [Erik Hatcher|http://www.blogscene.org/erik/]. It's basically just a couple of classes and a bunch of XDoclet templates. All these files are located in extras/appgen. |
|
At line 21 changed 6 lines. |
* Execute __ant ejbdoclet__ - this generates the PersonForm.java from the Person.java POJO. |
* From the command-line, navigate to "tools/strutsgen" |
* Execute __ant -Dform.name=PersonForm__ to generate two files in tools/strutsgen/build: |
** PersonForm.properties (labels for your form elements) |
** personForm.jsp (skeleton JSP file) |
* Copy the contents of PersonForm.properties into web/WEB-INF/classes/ApplicationResources.properties. ''You can remove the keys for personForm.saved and personForm.unsaved - these are properties from the BaseObject that we don't used in our Forms. If you do this, make sure and remove the rows (<tr>'s) containing these properties in personForm.jsp. Here is an example of what you might add to ApplicationResources.properties:'' |
* From the command-line, navigate to "extras/appgen" |
* Execute __ant -Dobject.name=Person -Dappgen.type=pojo -Dapp.module=__ to generate a bunch of files in extras/appgen/build/gen. In fact, it'll generate all the files you need to complete this tutorial. However, let's just grab the ones you need. |
** web/WEB-INF/classes/Person.properties (labels for your form elements) |
** web/pages/personForm.jsp (JSP file for viewing a single Person) |
** web/pages/personList.jsp (JSP file for viewing a list of People) |
* Copy the contents of Person.properties into web/WEB-INF/classes/ApplicationResources.properties. These are all the keys you will need for titles/headings and form properties. Here is an example of what you should add to ApplicationResources.properties: |
At line 30 removed 1 line. |
personForm.firstName=First Name |
At line 59 added 1 line. |
personForm.firstName=First Name |
At line 33 removed 3 lines. |
}}} |
</div> |
* Copy personForm.jsp to web/personForm.jsp |
At line 37 changed 1 line. |
We copy the JSP to the ''web'' folder instead of ''web/pages'' because the ''pages'' directory ends up in ''WEB-INF/pages'' when the application is packaged into a [WAR|http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/WCC3.html#69310] file. This is a [recommended practice|http://husted.com/struts/catalog.html] when building secure web applications. |
person.added=Person has been added successfully. |
person.updated=Person has been updated successfully. |
person.deleted=Person has been deleted successfully. |
At line 39 changed 1 line. |
;: ''The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from the ActionServlet. Placing all JSPs below WEB-INF ensures they are only accessed through Actions, and not directly by the client or each other. This allows security to be moved up into the Controller, where it can be handled more efficiently, and out of the base presentation layer.'' |
# -- person list page -- |
personList.title=Person List |
personList.heading=Persons |
At line 41 changed 1 line. |
The web application security for AppFuse specifies that all *.do url-patterns should be protected. This guarantees 1) all Actions are protected, and 2) you must go through an Action to get to a JSP (or at least the ones in ''pages''). |
# -- person detail page -- |
personDetail.title=Person Detail |
personDetail.heading=Person Information |
}}} |
</div> |
* Copy personForm.jsp to web/pages/personForm.jsp. Copy personList.jsp to web/pages/personList.jsp. |
At line 43 changed 1 line. |
All this is to say that __putting the personForm.jsp in the ''web'' folder will allow us to view it without making a Tile for it__. ''We'll get to that in the next tutorial.'' |
;: ''The files in the "pages" directory will end up in "WEB-INF/pages" at deployment time. The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from Struts' ActionServlet. Placing all JSPs below WEB-INF ensures they are only accessed through Actions, and not directly by the client or each other. This allows security to be moved up into the Action, where it can be handled more efficiently, and out of the base presentation layer.'' |
At line 45 changed 1 line. |
At this point, you won't be able to view this JSP in your browser because the <html:form> tag in personForm.jsp has ''action=""'' which is not allowed. You can try it yourself by setting up AppFuse on Tomcat using __ant setup-db setup-tomcat deploy__. |
The web application security for AppFuse specifies that all *.html url-patterns should be protected (except for /signup.html and /passwordHint.html). This guarantees that clients must go through an Action to get to a JSP (or at least the ones in ''pages''). |
At line 47 changed 5 lines. |
Then, start Tomcat and then go to [http://localhost:8080/appfuse/personForm.jsp]. This will result in the following error: |
{{{ |
javax.servlet.jsp.JspException: Cannot retrieve mapping for action / |
}}} |
Therefore, we need to create an Action for this JSP, and we should probably create a Test before we write our Action. |
%%note __NOTE:__ 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 } }}}%% |
At line 53 changed 2 lines. |
!!Create a new ActionTest to test our Action [#2] |
To create a StrutsTestCase Test for our Action, start by creating a PersonActionTest.java file in the test/web/**/action directory. |
* Add keys in ApplicationResources.properties the titles and headings in the JSPs |
In the generated JSPs, there are two keys for the title (top of the browser window) and the header (heading in the page). These fields are provided above with key names of personDetail.title and personDetail.heading. |
At line 56 changed 1 line. |
;:%%(color: blue)''As usual, copy → save as an existing ActionTest (i.e. UserActionTest). Replace [[Uu]ser with [[P]erson. You might want to make sure [Cactus|http://jakarta.apache.org/cactus] (StrutsTestCase is an extension of Cactus] tests are running before you copy an existing one. Run __ant test-cactus -Dtestcase=UserAction__ to verify the UserAction works. Stop Tomcat before you do this.''%% |
''Just above, we added "personForm.*" keys to this file, so why do I use personDetail instead of personForm for the titles and headings? The best reason is because it gives a nice separation between form labels and text on the page. Another reason is because all the *Form.* give you a nice representation of all the fields in your database. |
At line 58 changed 1 line. |
If you did copy UserActionTest, make sure and change ''UserFormEx'' to ''PersonForm''. The reason for ''UserFormEx'' is to extend the UserForm and put custom web-only properties or methods into our form. Since the UserForm is generated, it's not very feasibly to do it in the User.java object. |
I recently had a client who wanted all fields in the database searchable. This was fairly easy to do. I just looked up all the keys in ApplicationResources.properties which contained "Form." and then put them into a drop-down. On the UI, the user was able to enter a search term and select the column they wanted to search. I was glad I followed this Form vs. Detail distinction on that project!'' |
At line 60 removed 1 line. |
When we do create an Action (in [step 3|3]}, we're only going to create an __execute__ method, rather than all the different CRUD methods. So let's just test that method to start. |
At line 62 changed 1 line. |
[{Java2HtmlPlugin |
!!Create PersonActionTest to test PersonAction [#3] |
To create a StrutsTestCase Test for PersonAction, start by creating a PersonActionTest.java file in the test/web/**/action directory: |
At line 95 added 2 lines. |
[{Java2HtmlPlugin |
|
At line 66 changed 2 lines. |
public class PersonActionTest extends BaseStrutsTestCase { |
//~ Constructors =========================================================== |
import org.appfuse.Constants; |
import org.appfuse.webapp.form.PersonForm; |
At line 102 added 2 lines. |
public class PersonActionTest extends BaseStrutsTestCase { |
|
At line 73 changed 1 line. |
//~ Methods ================================================================ |
public void testEdit() throws Exception { |
setRequestPathInfo("/editPerson"); |
addRequestParameter("method", "Edit"); |
addRequestParameter("id", "1"); |
actionPerform(); |
At line 75 changed 1 line. |
public void testExecute() throws Exception { |
verifyForward("edit"); |
assertTrue(request.getAttribute(Constants.PERSON_KEY) != null); |
verifyNoActionErrors(); |
} |
|
public void testSave() throws Exception { |
At line 121 added 1 line. |
addRequestParameter("method", "Edit"); |
At line 123 added 1 line. |
|
At line 126 added 14 lines. |
PersonForm personForm = |
(PersonForm) request.getAttribute(Constants.PERSON_KEY); |
assertTrue(personForm != null); |
|
setRequestPathInfo("/savePerson"); |
addRequestParameter("method", "Save"); |
|
// update the form from the edit and add it back to the request |
personForm.setLastName("Feltz"); |
request.setAttribute(Constants.PERSON_KEY, personForm); |
|
actionPerform(); |
|
verifyForward("edit"); |
At line 83 changed 2 lines. |
public static void main(String[] args) { |
junit.textui.TestRunner.run(PersonActionTest.class); |
public void testRemove() throws Exception { |
setRequestPathInfo("/editPerson"); |
addRequestParameter("method", "Delete"); |
addRequestParameter("id", "2"); |
actionPerform(); |
|
verifyForward("mainMenu"); |
verifyNoActionErrors(); |
At line 89 changed 1 line. |
Everything should compile at this point (ant compile-web) since we're not referring to the PersonAction directly in our test. However, if you try to run __ant test-cactus -Dtestcase=PersonAction__, it won't work (make sure Tomcat is ''not'' running if you decide to try this). |
You will need to add __PERSON_KEY__ as a variable to the src/dao/**/Constants.java class. The name, "personForm", matches the name given to the form in the struts-config.xml file. |
At line 91 removed 3 lines. |
!!Create a new Action [#3] |
Now we have to create an Action (a.k.a. the Controller) to talk to our Manager and retrieve/save our data. In src/web/**/action, create a PersonAction.java file with the following contents: |
|
At line 159 added 14 lines. |
/** |
* The request scope attribute that holds the person form. |
*/ |
public static final String PERSON_KEY = "personForm"; |
}] |
|
If you try to run this test, you will get a number of NoSuchMethodErrors - so let's define the edit, save, and delete methods in the PersonAction class. |
|
!!Create PersonAction [#4] |
|
In src/web/**/action, create a PersonAction.java file with the following contents: |
|
[{Java2HtmlPlugin |
|
At line 101 removed 2 lines. |
import org.apache.commons.logging.Log; |
import org.apache.commons.logging.LogFactory; |
At line 181 added 2 lines. |
import org.apache.struts.action.ActionMessage; |
import org.apache.struts.action.ActionMessages; |
At line 184 added 3 lines. |
import org.appfuse.model.Person; |
import org.appfuse.service.PersonManager; |
import org.appfuse.webapp.form.PersonForm; |
At line 109 removed 4 lines. |
* Implementation of <strong>Action</strong> that interacts with the {@link |
* PersonForm} and retrieves values. It interacts with the {@link |
* PersonManager} to retrieve/persist values to the database. |
* |
At line 114 changed 1 line. |
* validate="false" parameter="action" input="mainMenu" |
* validate="false" parameter="method" input="mainMenu" |
At line 117 changed 1 line. |
//~ Instance fields ======================================================== |
|
public ActionForward cancel(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
return mapping.findForward("mainMenu"); |
} |
At line 119 changed 1 line. |
private Log log = LogFactory.getLog(PersonAction.class); |
public ActionForward delete(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'delete' method"); |
} |
At line 121 changed 1 line. |
//~ Methods ================================================================ |
ActionMessages messages = new ActionMessages(); |
PersonForm personForm = (PersonForm) form; |
At line 123 changed 3 lines. |
public ActionForward execute(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
// Exceptions are caught by ActionExceptionHandler |
PersonManager mgr = (PersonManager) getBean("personManager"); |
mgr.removePerson(personForm.getId()); |
|
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.deleted")); |
|
// save messages in session, so they'll survive the redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("mainMenu"); |
} |
|
public ActionForward edit(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
At line 128 changed 1 line. |
log.debug("Entering 'execute' method"); |
log.debug("Entering 'edit' method"); |
At line 131 changed 2 lines. |
// return nothing (yet) |
return null; |
PersonForm personForm = (PersonForm) form; |
|
// if an id is passed in, look up the user - otherwise |
// don't do anything - user is doing an add |
if (personForm.getId() != null) { |
PersonManager mgr = (PersonManager) getBean("personManager"); |
Person person = mgr.getPerson(personForm.getId()); |
personForm = (PersonForm) convert(person); |
updateFormBean(mapping, request, personForm); |
} |
|
return mapping.findForward("edit"); |
At line 246 added 39 lines. |
|
public ActionForward save(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'save' method"); |
} |
|
// Extract attributes and parameters we will need |
ActionMessages messages = new ActionMessages(); |
PersonForm personForm = (PersonForm) form; |
boolean isNew = ("".equals(personForm.getId())); |
|
if (log.isDebugEnabled()) { |
log.debug("saving person: " + personForm); |
} |
|
PersonManager mgr = (PersonManager) getBean("personManager"); |
Person person = (Person) convert(personForm); |
mgr.savePerson(person); |
|
// add success messages |
if (isNew) { |
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.added")); |
|
// save messages in session to survive a redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("mainMenu"); |
} else { |
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.updated")); |
saveMessages(request, messages); |
|
return mapping.findForward("edit"); |
} |
} |
At line 137 changed 2 lines. |
We're not putting much in PersonAction at this point because we just want to 1) render the JSP and 2) verify our Test runs. The XDoclet tags (beginning with ''@struts.action'') will generate the following XML in the build/appfuse/WEB-INF/struts-config.xml file (when you run __ant webdoclet__): |
[{Java2HtmlPlugin |
You'll notice in the code above that there are many calls to to ''convert'' a PersonForm or a Person object. The ''convert'' method is in BaseAction.java (which calls ConvertUtil.convert()) and |
uses |
[BeanUtils.copyProperties|http://jakarta.apache.org/commons/beanutils/api/org/apache/commons/beanutils/BeanUtils.html#copyProperties(java.lang.Object,%20java.lang.Object)] |
to convert POJOs → ActionForms and ActionForms → POJOs. |
At line 140 changed 5 lines. |
<action path="/editPerson" type="org.appfuse.webapp.action.PersonAction" |
name="personForm" scope="request" input="mainMenu" |
parameter="action" unknown="false" validate="false"> |
</action> |
}] |
;:''If you are running Eclipse, you might have to "refresh" the project in order to see PersonForm. It lives in build/web/gen, which should be one of your project's source folders. This is the only way for Eclipse to see and import PersonForm, since it is generated by XDoclet and does not live in your regular source tree. You can find it at build/web/gen/org/appfuse/webapp/form/PersonForm.java. |
At line 146 changed 1 line. |
;:''I formatted the XML above the the purposes of the tutorial. No content has changed.'' |
;:You can also configure Eclipse to auto-refresh your workspace using: Window > Preferences > General > Workspace > Refresh Automatically.'' |
At line 148 changed 1 line. |
Now we need to change the "action" attribute in the <personForm.jsp> to be ''action="editPerson"''. |
;:''In [BaseAction|http://raibledesigns.com/downloads/apptracker/api/org/appfuse/webapp/action/BaseAction.java.html] you can register additional Converters (i.e. [DateConverter|http://raibledesigns.com/downloads/apptracker/api/org/apptracker/util/DateConverter.java.html]) so that BeanUtils.copyProperties knows how to convert Strings → Objects. If you have Lists on your POJOs (i.e. for parent-child relationships), you will need to manually convert those using the {{convertLists(java.lang.Object)}} method.'' |
At line 299 added 2 lines. |
Now you need to add the ''edit'' forward and the ''savePerson'' action-mapping, both which are specified in in the PersonActionTest. To do this, add a couple more XDoclet tags to the top of the PersonAction.java file. Do this right above the class declaration. You should already have the XDoclet tag for the ''editPerson'' action-mapping, but I'm showing it here so you can see all the XDoclet tags at the top of this class. |
|
At line 152 changed 2 lines. |
<html:form action="editPerson" method="post" styleId="personForm" |
focus="" onsubmit="return validatePersonForm(this)"> |
/** |
* @struts.action name="personForm" path="/editPerson" scope="request" |
* validate="false" parameter="method" input="mainMenu" |
* |
* @struts.action name="personForm" path="/savePerson" scope="request" |
* validate="true" parameter="method" input="edit" |
* |
* @struts.action-forward name="edit" path="/WEB-INF/pages/personForm.jsp" |
*/ |
public final class PersonAction extends BaseAction { |
At line 156 changed 1 line. |
Everything is almost done for this tutorial, let's get to running our tests! |
The main difference between the ''editPerson'' and ''savePerson'' action-mappings is that ''savePerson'' has validation turned on (see validation="true") in the XDoclet tag above. Note that the "input" attribute must refer to a forward, and cannot be a path (i.e. /editPerson.html). If you'd prefer to use the save path for both edit and save, that's possible too. Just make sure validate="false", and then in your "save" method - you'll need to call form.validate() and handle errors appropriately. |
At line 158 changed 1 line. |
!!Display the JSP in a browser and run the ActionTest [#4] |
You might notice that the code you're using to call the PersonManager is the same as the code used in the PersonManagerTest. Both PersonAction and PersonManagerTest are ''clients'' of PersonManagerImpl, so this makes perfect sense. |
At line 160 changed 3 lines. |
To test the JSP visually in your browser, save everything, run __ant deploy__, start Tomcat, and navigate to [http://localhost:8080/appfuse/personForm.jsp]. You should see something similar to the following image in your browser: |
%%(border: 1px solid black; height: 125px; width: 270px; margin: 0 auto;) |
[personForm-plain.png] |
Everything is almost done for this tutorial, let's get to running the tests! |
|
!!Run PersonActionTest [#5] |
|
If you look at our PersonActionTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so add that to our sample data file (metadata/sql/sample-data.xml). I'd add it at the bottom - order is not important since it (currently) does not relate to any other tables. |
|
{{{ |
<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>James</value> |
<value>Davidson</value> |
</row> |
</table> |
}}} |
|
DBUnit loads this file before we run any of our tests, so this record will be available to the PersonActionTest. |
|
Now if you run __ant test-web -Dtestcase=PersonAction__ - everything should work as planned. Make sure Tomcat isn't running before you try this. |
|
%%(color:green)BUILD SUCCESSFUL\\ |
Total time: 1 minute 21 seconds%% |
|
!!Clean up the JSP to make it presentable [#6] |
|
Now let's clean up the generated personForm.jsp. Change the ''action'' of the <html:form> to be "savePerson" so validation will be turned on when saving. Also, change the ''focus'' attribute from focus="" to focus="firstName" so the cursor will be in the firstName field when the page loads (this is done with JavaScript). |
|
Another thing you will need to do is comment out the following lines at the bottom of the personForm.jsp. This is because the Validator will throw an exception if a formName is specified and no validation rules exist for it. |
|
;:''Personally, I think this is [a bug|http://nagoya.apache.org/bugzilla/show_bug.cgi?id=27316], but the Struts Committers disagreed.'' |
|
{{{<html:javascript formName="personForm" cdata="false" |
dynamicJavascript="true" staticJavascript="false"/> |
<script type="text/javascript" |
src="<html:rewrite page="/scripts/validator.jsp"/>"></script>}}} |
|
Now if you execute __ant db-load deploy__, start Tomcat and point your browser to [http://localhost:8080/appfuse/editPerson.html?id=1], you should see something like this: |
|
%%(border: 1px solid black; margin: 0 auto; height: 166px; width: 337px) |
[personForm-final.png] |
At line 164 removed 1 line. |
;:''There is also a __deploy-web__ target in build.xml that will allow you to just deploy the files in the web directory. Nothing gets compiled or generated when you use this target.'' |
At line 166 removed 1 line. |
Now, if you stop Tomcat and run __ant test-cactus -Dtestcase=PersonAction__, that should work too! |
At line 370 added 108 lines. |
%%note __NOTE:__ Use the __deploy-web__ target if you've changed any files under the ''web'' directory. Otherwise, use __deploy__ which compiles and deploys.%% |
|
Finally, to make this page more user friendly, you may want to add a message for your users at the top of the form, which can easily be done by adding text (using <fmt:message>) at the top of the personForm.jsp page. |
|
!![[Optional] Create a Canoo WebTest to test browser-like actions [#7] |
The final (optional) step in this tutorial is to create a [Canoo WebTest|http://webtest.canoo.com] to test the JSPs. |
|
;:''I say this step is optional, because you can run the same tests through your browser.'' |
|
You can use the following URLs to test the different actions for adding, editing and saving a user. |
|
* Add - [http://localhost:8080/appfuse/editPerson.html]. |
* Edit - [http://localhost:8080/appfuse/editPerson.html?id=1] (make sure and run __ant db-load__ first). |
* Delete - [http://localhost:8080/appfuse/editPerson.html?method=Delete&id=1] (or edit and click on the Delete button). |
* Save - Click [edit|http://localhost:8080/appfuse/editPerson.html?id=1] and then click the Save button. |
|
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 test/web/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. |
|
;:''I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing ''-Dtestcase=Name'', I've found that I'm used to doing CamelCase for my JUnit Tests.'' |
|
[{Java2HtmlPlugin |
|
<!-- runs person-related tests --> |
<target name="PersonTests" |
depends="EditPerson,SavePerson,AddPerson,DeletePerson" |
description="Call and executes all person test cases (targets)"> |
<echo>Successfully ran all Person JSP tests!</echo> |
</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="click Edit Person link" url="/editPerson.html?id=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="click Edit Person link" url="/editPerson.html?id=1"/> |
<verifytitle description="we should see the personDetail title" |
text=".*${personDetail.title}.*" regex="true"/> |
<setinputfield description="set lastName" name="lastName" value="Canoo"/> |
<clickbutton label="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="click Add Button" url="/editPerson.html"/> |
<verifytitle description="we should see the personDetail title" |
text=".*${personDetail.title}.*" regex="true"/> |
<setinputfield description="set firstName" name="firstName" value="Abbie"/> |
<setinputfield description="set lastName" name="lastName" value="Raible"/> |
<clickbutton label="${button.save}" description="Click button 'Save'"/> |
<verifytitle description="Main Menu appears if save successful" |
text=".*${mainMenu.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="click Edit Person link" url="/editPerson.html?id=1"/> |
<prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/> |
<clickbutton label="${button.delete}" description="Click button 'Delete'"/> |
<verifyNoDialogResponses/> |
<verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/> |
<verifytext description="verify success message" text="${person.deleted}"/> |
</steps> |
</webtest> |
</target> |
}] |
|
After adding this, you should be able to run __ant test-canoo -Dtestcase=PersonTests__ with Tomcat running or __ant test-jsp -Dtestcase=PersonTests__ if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target. |
|
You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add the following between </webtest> and </target> at the end of each target. |
|
{{{<loadfile property="web-tests.result" |
srcFile="${test.dir}/data/web-tests-result.xml"/> |
<echo>${web-tests.result}</echo>}}} |
|
At line 169 changed 1 line. |
Total time: 51 seconds%% |
Total time: 11 seconds%% |
At line 171 removed 1 line. |
;:''Look in your console's log for <span style="color: purple">PersonAction.execute(33) | Entering 'execute' method</span>. This is the log.debug statement we put in our execute method. You should also be able to [view personForm.jsp|http://localhost:8080/appfuse/personForm.jsp] (make sure Tomcat is running and you've logged into AppFuse) and click the "Save" button to see the same debug message.'' |
At line 175 changed 1 line. |
''Next Up:'' __Part IV:__ [Configuring Tiles and Action CRUD methods|ConfiguringTiles] - Integrating personForm.jsp with Tiles, replacing execute with different CRUD methods (add, edit, delete), customizing the JSP so it looks good and finally - writing a WebTest to test the JSPs functionality. |
''Next Up:'' __Part IV:__ [Adding Validation and List Screen|ValidationAndList] - 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. |