Raible's Wiki
Raible Designs AppFuseHomepage- Korean - Chinese - Italian - Japanese QuickStart Guide User Guide Tutorials Other ApplicationsStruts ResumeSecurity Example Struts Menu
Set your name in
UserPreferences
Referenced by
JSPWiki v2.2.33
Hide Menu |
AJAX using DWR in AppFuseThis 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.
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:
<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>
<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
<!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.
Followed by a Pet class.
Now we only need to let a Person know it has a Job and might have some pets.
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.
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.
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<convert converter="bean" match="org.appfuse.*"/> <create creator="spring" javascript="personDetailDWRManager"> <param name="beanName" value="personDetailManager"></param> </create> The 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 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 <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: 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]SecurityAn 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 :-)I18NAnother 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:
|