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
AppFuseAjaxWithDWR_C...
Articles
CreateAnAJAXBasedFil...




JSPWiki v2.2.33

[RSS]


Hide Menu

AppFuseAjaxWithDWR


AJAX using DWR in AppFuse

This article will give a little introduction into using AJAX in AppFuse by means of DWR. In AppFuse 1.9 DWR is integrated already and everything is set up for usage so there is no further need for configuration. Notice that AppFuse ships with script.aculo.us as well, but this will not be covered here (haven't tried it yet).

This article assumes that you have a running AppFuse project and have made you way through the tutorials. So your project will contain the famous Person class, a PersonDao and a PersonManager as well as the PersonAction and the jsp pages for viewing and editing a single Person as well as the list screen. Here we will enhance the person list screen to show some additional information when the user moves the mouse over a person's name.

  • [0] Configure DWR servlet
  • [1] Enhance the model
  • [2] Create a 'DWR Manager'
  • [3] Let DWR know of the manager and test it
  • [4] Create the client code
  • [5] Some open questions about DWR/JavaScript.

Configure DWR servlet [#0]

If you have an AppFuse 1.9 project you can skip this part as DWR is configured already. You can verify this by opening web/WEB-INF/web.xml and look for the definition of a servlet named dwr-invoker. If you use an older version of AppFuse (or not an AppFuse based project at all) this servlet and the mapping fot it will have to be defined (which is fairly simple). In AppFuse versions below 1.9 web.xml was split into several files and put together during deployment. Here's what remains to be done:
  • Download DWR and install it in you AppFuse app (copy the jar to lib/dwr/ insert an entry in lib/lib.properties and make sure it gets copied to your server on deploy).
  • Open metadata/web/servlets.xml and append the servlet definition like this
	<servlet>
	  <servlet-name>dwr-invoker</servlet-name>
	  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
	  <init-param>
	  <param-name>debug</param-name>
	  <param-value>true</param-value>
	</init-param>
	</servlet>
  • Open metadata/web/servlet-mappings.xml and append the servlet mapping for the dwr-invoker
	<servlet-mapping>
	  <servlet-name>dwr-invoker</servlet-name>
	  <url-pattern>/dwr/*</url-pattern>
	</servlet-mapping>
That's actually it. On the next deployment you should find this information in your generated web.xml and the DWR servlet will be ready to respond to DWR calls. We still need a configuration file to configure what managers DWR will take care about so
  • Create a file called dwr.xml in the web/WEB-INF/ directory with these contents.
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
    "http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
    <allow>
    </allow>
</dwr>
We will fill this file later.

Enhance the model [#1]

In order to show some additional information we will of course first need that info's :-) After a 5 minute meditation on what I might add to a Person my tortured mind came up with trivial things like a Job and a set of Pets. Here's the important code snippets for the Job class.


package org.appfuse.model;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * A Person's Job.
 *
 @author <a href="mailto:[email protected]">Josip Mihelko</a>
 @version $Revision$
 * @hibernate.class table = "job"
 */
public class Job extends BaseObject
{
  private static final long serialVersionUID = -1;
  private Long              id;
  private String            jobName;
  private String            description;

  private Person            person;

  //getters, setters, xdoclet and the abstract methods from BaseObject omitted for brevity.
  //please refer to the attached source files.
}

Followed by a Pet class.


package org.appfuse.model;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;


/**
 * A person's pet. A pet has a name and the information od what kind of animal it is.
 *
 @author <a href="mailto:[email protected]">Josip Mihelko</a>
 @version $Revision$
 * @hibernate.class table = "pet"
 */
public class Pet extends BaseObject
{
  private static final long serialVersionUID = -1;
  private Long              id;
  private String            animal;
  private String            petName;
  private Person            owner;

  //getters, setters, xdoclet and the abstract methods from BaseObject omitted for brevity.
  //please refer to the attached source files.
}

Now we only need to let a Person know it has a Job and might have some pets.


package org.appfuse.model;

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * Eine Person.
 *
 @author <a href="mailto:[email protected]">Josip Mihelko</a>
 @version $Revision: 1.1 $
 * @hibernate.class table = "person"
 */
public class Person extends BaseObject
{
  private static final long serialVersionUID = 0;
  private Long              id;
  private String            firstName;
  private String            lastName;
  private Job               job;
  private Set               pets = new HashSet();

  /**
   @return The person's job.
   * @hibernate.one-to-one class = "org.appfuse.model.Job" constrained = "false" property-ref = "person"
   */
  public Job getJob()
  {
    return job;
  }

  /**
   @param job New job of the person.
   */
  public void setJob(Job job)
  {
    this.job = job;
  }

  /**
   @return The person's pets.
   * @hibernate.set inverse = "true" name="pets" cascade="delete"  lazy="true"
   * @hibernate.key column="owner"
   * @hibernate.one-to-many class="org.appfuse.model.Pet"
   */
  public Set getPets()
  {
    return pets;
  }

  /**
   @param pets New pet's of the person.
   */
  public void setPets(Set pets)
  {
    this.pets = pets;
  }
  
  //rest of the class ommitted for brevity
  //please refer to the attached source files.
}

Next we add some sample data so we can actually see something. Add the following data to metadata/sql/sample-data.xml beneath the person table.

  <table name="job">
    <column>id</column>
    <column>person</column>
    <column>description</column>
    <column>name</column>
    <row>
    	<value>1</value>
    	<value>1</value>
    	<value>The brain behind AppFuse.</value>
    	<value>Developer Guru</value>
    </row>
    <row>
    	<value>2</value>
    	<value>2</value>
    	<value>Most powerfull person in the world</value>
    	<value>President of the USA</value>
    </row>
  </table>
  <table name="pet">
    <column>id</column>
    <column>owner</column>
    <column>animal</column>
    <column>petname</column>
    <row>
    	<value>1</value>
    	<value>1</value>
    	<value>Fish</value>
    	<value>George</value>
    </row>
    <row>
    	<value>2</value>
    	<value>2</value>
    	<value>Cat</value>
    	<value>India</value>
    </row>
    <row>
    	<value>3</value>
    	<value>2</value>
    	<value>Goat</value>
    	<value>Scape</value>
    </row>
  </table>

Don't forget to insert the newly created classs to the class mapping in applicationContext-hibernate. After that your sessionFactory classmapping should look something like this:

            <list>
                <value>org/appfuse/model/Role.hbm.xml</value>
                <value>org/appfuse/model/User.hbm.xml</value>
                <value>org/appfuse/model/Person.hbm.xml</value>
                <value>org/appfuse/model/Job.hbm.xml</value>
                <value>org/appfuse/model/Pet.hbm.xml</value>
            </list>
Now you can run ant setup-db and your db will be ready for the rest of the tutorial.

Create a 'DWR Manager' [#2]

Next we will create a Manager that will be exposed to DWR. I think it's good practice not to expose any managers you wrote to DWR but write special ones for that task. It still is a usual Manager just like the PersonManager from the tutorial. Insert a new package org.appfuse.service.dwr and create an interface for the DWR manager called PersonDetailManager.


package org.appfuse.service.dwr;

import org.appfuse.model.viewwrappers.JobDetail;

import org.appfuse.service.PersonManager;

import java.util.List;

/**
 * A DWR-Manager that will be called by javascript.
 *
 @author <a href="mailto:[email protected]">Josip Mihelko</a>
 @version $Revision$
 */
public interface PersonDetailManager
{
  /**
   @param personManager The PersonManager.
   @see PersonManager
   */
  public void setPersonManager(PersonManager personManager);

  /**
   * Returns a list of PetDetail-objects.
   *
   @param personID ID of the person who's pets are of interest.
   *
   @return List of PetDetails.
   */
  public List getPets(String personID);

  /**
   * Returns a JobDetail-object with the contents of the person's job.
   *
   @param personID ID of the person who's ojb is of interest.
   *
   @return JobDetail object holding the information of the person's job.
   */
  public JobDetail getJob(String personID);
}

This manager is a client of the PersonManager and the methods expect a person's id (as a String) and will return the additional information for each person. Notice that the manager does not return the actual model objects but very simple wrapper objects that simply hold the same information as the model objects themselves. They are called JobDetail and PetDetail. These wrappers consist of very simple String properties and the corresponding getters and therefore I ommitt them here for brevity. They can be found in the source attachment. This wrapping is not neccessary and everything should work with the real objects as well. I do that because I think it is good practice not to expose the model objects in the browser. Having simple objects with hibernate.poperties only will not be a problem, but as soon as you have more complex, probably lazily initialized relationships calling getters of thos properties will result in a LazyInitializationException beeing thrown... Let's implement that interface. Create a new class called PersonDetailManagerImpl in the impl package.


package org.appfuse.service.dwr.impl;

import org.appfuse.model.Person;
import org.appfuse.model.Pet;
import org.appfuse.model.viewwrappers.JobDetail;
import org.appfuse.model.viewwrappers.PetDetail;

import org.appfuse.service.PersonManager;
import org.appfuse.service.dwr.PersonDetailManager;
import org.appfuse.service.impl.BaseManager;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 @see org.appfuse.service.dwr.PersonDetailManager
 *
 @author <a href="mailto:[email protected]">Josip Mihelko</a>
 @version $Revision$
 */
public class PersonDetailManagerImpl extends BaseManager implements PersonDetailManager
{
  private PersonManager personManager;

  /**
   @see org.appfuse.service.dwr.PersonDetailManager#setPersonManager(org.appfuse.service.PersonManager)
   */
  public void setPersonManager(PersonManager personManager)
  {
    this.personManager = personManager;
  }


  /**
   @see org.appfuse.service.dwr.PersonDetailManager#getPets(java.lang.String)
   */
  public List getPets(String personID)
  {
    Person person   = personManager.getPerson(personID);
    Set    pets     = person.getPets();
    List   toReturn = new ArrayList();
    for (Iterator iter = pets.iterator(); iter.hasNext();)
      {
      Pet       pet       = (Petiter.next();
      PetDetail petDetail = new PetDetail(pet);
      toReturn.add(petDetail);
      }
    return toReturn;
  }

  /**
   @see org.appfuse.service.dwr.PersonDetailManager#getJob(java.lang.String)
   */
  public JobDetail getJob(String personID)
  {
    Person    person    = personManager.getPerson(personID);
    JobDetail jobDetail = new JobDetail(person.getJob());
    return jobDetail;
  }
}

Rather simple, isn't it. Load the person get the person's Job / Pets, create a wrapper object for each and return it. Add the following declaration in applicationContext-service.xml under the declaration of the PersonManager:

    <bean id="personDetailManager" parent="txProxyTemplate">
    	<property name="target">
    		<bean class="org.appfuse.service.dwr.impl.PersonDetailManagerImpl">
    			<property name="personManager" ref="personManager"></property>
    		</bean>
    	</property>
    </bean>

So far, nothing new actually. But now we are going to make this manager known to DWR.

Let DWR know of the manager and test it [#3]

Open web/WEB-INF/dwr.xml and insert following code within the tag.
        <convert converter="bean" match="org.appfuse.*"/>
        <create creator="spring" javascript="personDetailDWRManager">
        	<param name="beanName" value="personDetailManager"></param>
        </create>

The tag will tell DWR what objects will be allowed for usage with dwr. For simplicity I used a wildcard here that will let DWR convert all objects in the packages org.appfuse an beneath. From a security perspective of course you would only allow conversion of the objects you really need.

By default DWR will assume your managers return Strings and lists of Strings. So if you would like to send something else to your page like Integers you will have to define a signature within dwr.xml for each method call.

The tag will tell how to name the JavaScript manager that can be use on the client side and what object should respond to calls on that JavaScript object. Things will lighten up very soon. Now you can run ant deploy and start you Tomcat. Login to you app and navigate to http://localhost:8080/appfuse/dwr/ and have a look at a really cool feature of DWR. It provides a page showing all managers known to DWR and even let's you test calls to that managers. Try it: Click on the personDetailManager and look for the getPets() and getJob() methods. If you execute one of the methods with an id of an existing person the corresponding data should be shown in a JavaScript alert. Take a moment to read the page, it will give you some additional information on DWR, especially some frequently made errors (don't use overloaded methods, how to fix missing converters). It also tells you what url you have to include to make use of the personDetailManager. When you verified that you manager behaves as expected you're ready for the [last part]...

Create the client code [#4]

We will rewrite the personList.jsp in order to include a onMouseOver behaviour to show up details about a person's pets and job whenever the user moves the mouse over the peron's name. Notice that I wrote this article using an AppFuse instance with WebWork, so the JSP might difer, but for understanding the JavaScript that should not matter.

First we will have to rewrite the existing person table that is constructed using the tag. I do that because I don't know how to add onClick and onMouse behaviour using the tag. If anybode knows about that please let me know at josip (at) esistegalaber (dot) de. The table itself is rather simple: Iterate over the persons property of the PersonAction and create a row for each person.

<table cellspacing="0" cellpadding="0" class="list" border="1">
 <thead>
  <tr>
	<th bgcolor="#CCCCCC"><fmt:message key="person.firstName"/></th>
	<th bgcolor="#CCCCCC"><fmt:message key="person.lastName"/></th>
	<th bgcolor="#CCCCCC"><fmt:message key="person.id"/></th>
  </tr>
 </thead>
  <tbody id="personList">
  <ww:iterator value="persons">
   <tr>
	 <td>
	 		<ww:property value="firstName"/>
	 </td>
	 <td>
	 		<ww:property value="lastName"/>
	 </td>
	 <td>
	 <ww:property value="id"/> 
         <a href='<c:url value="editPerson.html?"/>id=<ww:property value="id"/>'><fmt:message key="person.edit"/></a>
	 </td>
   </tr>
  </ww:iterator>
 </tbody>
</table>

Well, it won't win any designer awards, but I'm not a designer anyway :-) Next I add an additional hidden div that will contain another 2 tables (beeing empty when the page is loaded) where we will put the person details in on mouseover. Append the following code in the personList.jsp:

<div id="detailDiv" style="visibility:hidden">
	<table cellspacing="0" cellpadding="0" class="list">
		<thead>
			<tr>
				<th width="50%"><fmt:message key="person.jobname.heading"/></th>
				<th width="50%"><fmt:message key="person.jobdesc.heading"/></th>
			</tr>
		</thead>
		<tbody id="jobTable">
		</tbody>
	</table>
	<br/>
	<table cellspacing="0" cellpadding="0" class="list">
		<thead>
			<tr>
				<th width="50%"><fmt:message key="person.petname.heading"/></th>
				<th width="50%"><fmt:message key="person.pettype.heading"/></th>
			</tr>
		</thead>
		<tbody id="petTable">
		</tbody>
	</table>
</div>

So now we are ready to get to the behaviour part. Add a link around the person's first an last name with onmouseover and onmouseout behaviour:

	 <a 
	 	href="#" 
	 	onmouseover="javascript:getDetails('<ww:property value="id"/>', '<ww:property value="firstName"/>')" 
	 	onmouseout="javascript:hideDetailDiv()">
	 		<ww:property value="firstName"/>
	 </a>

and the same for the lastName property

	 <a 
	 	href="#" 
	 	onmouseover="javascript:getDetails('<ww:property value="id"/>', '<ww:property value="firstName"/>')" 
	 	onmouseout="javascript:hideDetailDiv()">
	 		<ww:property value="lastName"/>
	 </a>

It's time to get to the JavaScript. We'll write a function called getDetails() that will call the DWR manager we exported in [3]. Therefore we have to include the following script:

<script type='text/javascript' src='/ajax-tutorial/dwr/interface/personDetailDWRManager.js'></script>
<script type='text/javascript' src='/ajax-tutorial/dwr/engine.js'></script>
<script type='text/javascript' src='/ajax-tutorial/dwr/util.js'></script>
The import of util.js is optional, but we will use some of the functionality in a moment. Now it's time to do the actual DWR call. Insert the function getDetails(personid, firstName).
	//a global variable to store the person's first name between server-calls.
	//calls to DWR-Managers perform asynchrounasly...
	var pickedPerson;

	//#
	//# This function gets called onMouseOver and retrieves the data from the server.
	//#
	function getDetails(personID, firstName)
	{
		//store the picked person name for further use
		pickedPerson = firstName;
		//retrive the job details from the server.
		personDetailDWRManager.getJob(personID, showJob);
		//show the div containing the details table.
		document.getElementById("detailDiv").style.visibility='visible';
	}
You will notice that the calls to our DWR manager have an additional parameter that cannot be found in the signature of our java manager object: personDetailDWRManager.getJob(personID, showJob); That additional parameter is the callback function that will be called by DWR after the server object returns its result. DWR is quite flexible in evaluating that callback parameter, it can be the first or the last parameter of the call. For further information on this fact (and anything else of course) visit the DWR documentation. That's actually the main magic in using DWR. Asynchronous server calls are executed and the results is passed back to a callback function where you can do with the data whatever you need. So now we need to write that callback and proccess the returned data. Insert the following functions:
	//#
	//# A callback funtion beeing called from DWR. The parameter is inserted by DWR and
	//# will be the result of the personDetailManager.
	//# It inserts the job information into the jobTable. This is done using DWRUtil.addRows.
	//#
	function showJob(job)
	{
		//clear all inner html from the table containing the job information.
		clearHtml(document.getElementById("jobTable"));
                //append a row in our jobTable for each job
		DWRUtil.addRows("jobTable", [job], [appendJobName, appendJobDescription]);
	}

	//#
	//# Utility function clearing a HTML-element of all children.
	//# Called to clear any previouse entered information from the detail tables.
	//#
	function clearHtml(node)
	{
		while(node.firstChild != null)
		{
			node.removeChild(node.firstChild);
		}
	}
Instead of creating a table row for the job "by hand" we use the really cool DWR utility function DWRUtil.addRows() that works on the same callback principle. The first parameter of this function is the id of the table to be filled. The second one is an array containing the data to be proccessed and the third parameter consist of an array of functions. Each function is called with each element of the data array and will simply return the contents of the table cell. So let's write the (in this case very simple) callback functions appendJobName and appendJobDescription.
	//#
	//# Another callback function beeing called from DWRUtil.addRows.
	//# It simply returnes the job's name.
	//#
	function appendJobName(job)
	{
		return pickedPerson + "'s job: " + job.jobname;
	}

	//#
	//# Yet another callback function beeing called from DWRUtil.addRows.
	//# It simply returns the job's description.
	//#
	function appendJobDescription(job)
	{
		return job.jobdesc;
	}

For the person's pets exactly the same procedure can be executed:
DWR-call -> callback function using addRows -> callback function to insert stuff into the table.
Either write it yourself or take a look at my complete personList.jsp here

Well that's it. Of course the same result could have been created without DWR but there are many cases where AJAX will improve the look and feel and the usage of your AppFuse webapps very much as it's simply unbeatable fast. Have fun!

Some open questions about DWR/JavaScript. [#5]

Security

An rather obvious security issue is the DWR debug site at /yourapp/dwr/. It can be turned off quite easily changing your web.xml (and should be in production mode). Find further information about DWR and security here. Actually security issues are not really the field I know the most about. You can secure your app to a huge extend using ACEGI (here's the tutorial), and of course anything you do to secure you usual service can be done to your DWR managers (see this little HowTo). Nonetheless I think one still wouldn't write online banking software using DWR, would you? Please prove me wrong if I am :-)

I18N

Another problem I have come across using DWR is I18N. In this tutorial we load information from the database using the usual AppFuse pattern and put that information into the site as is. But in most cases you would want to display an I18N message depending on the results returned from the server. Up to now I don't have a good solution to this but write all required messages to a dynamic file and reference the created JavaScript variables from my callback functions. This is not very elegant and I think the easiest way to I18N would be to access the ApplicaitonResource_xx.properties directly and get anything needed from there. Still haven't found a way to do that, but if you have any suggestions on this topic please let me know at josip(at)esistegalaber(dot)de.
Attachments:
ajax-tutorial.rar Info on ajax-tutorial.rar 77712 bytes


Go to top   Edit this page   More info...   Attach file...
This page last changed on 06-Nov-2006 13:53:00 MST by MatsHenricson.