Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Turbocharge Your Pages with AJAX, Dojo, and Struts : Page 2

Enhance users' sense of control with Dojo AJAX calls instead of page reloads. Use Dojo's AJAX API to cleanly invoke Struts actions.


advertisement
A Sorted Example
In order to understand how Dojo works with Struts and Tiles it will help to have a concrete problem to solve. To this end, the accompanying sample code introduces a list of famous people and displays it to the user as shown in Figure 1.

Dojo Woes: Living on the Bleeding Edge

While I really like Dojo, I did experience some difficulties with the back button and some rather unhelpful debug statements. I first installed Dojo 0.2.2 and had some issues getting the dojo.byId calls to work. Struts doesn't support the id property as a property of the html:hidden tag. IE blows right by this missing property during a dojo.byId("field") call and looks at the field's name. Firefox on the other hand throws an error and informs you that the field doesn't support any such property—meaning id. I'm extremely fortunate that one of my colleagues clued me in that the styleId field is the html:hidden tag's property that converts to id once the JSP renders. I also lucked out during the same conversation when another colleague mentioned that the changeUrl parameter must be added to the dojo.bind( ) method in order to get some semblance of back button functionality running.



If this is all starting to seem like work to you - you're not alone. As I noted, some colleagues of mine were very generous with their expertise in filling me in about the various hacks they performed to get the back button working. Even with their pointers I put in more hours than I care to remember hacking through some of these issues and new ones still pop up from time to time. I wish I could report that I got to the bottom of them all prior to going to press and that the sample code I wrote will clarify your every question but alas I can't make this claim. Moreover, certain idiosyncratic environmental issues—such as the number of Firefox extensions you have installed—might also contribute to some glitchy behavior.

After encountering some of these problems I upgraded my local version of the Dojo toolkit and now straddle both 0.2.2 and 0.3.1 in my incomplete, but growing Dojo knowledge. Little tweaks abound: I searched the web and found that the bootstrap1.js file sets the djConfig preventBackButtonFix value to 'true' by default. I was setting it to 'false' in my code but I changed the bootstrap1.js value as well and miraculously, a Firefox only error loading the page went away. The back button generally cooperates in Firefox but some of the updates of fields didn't go smoothly for me in 0.3.1 using the same techniques that I used successfully with 0.2.2.

I loaded up my sample app in Opera but an info message there told me explicitly that Opera isn't supported for back/forward button functionality but the rest of the code worked fine. Oddly, a colleague testing with the 0.2.2 version found Opera worked fine. The bottom line is that there are still some issues out there and while Dojo is under active development and headed in the right direction, you should expect some bumps in the road for now.

If you want to sort this data differently from the original presentation on the first page load you have two options. The first option is the old school way: submit the entire form back to the server to resort the data and reload the entire page. You probably guessed that the second option is to use AJAX. To implement the AJAX solution, mark up your page to include the 'sortable' list inside of a div block and use a button click or other client-side event, anywhere on the page, to invoke the AJAX call to resort the data as shown in Figure 2.


Figure 1. This is what you should see after the Dojo call is invoked to sort the list.
 
Figure 2. The list has been quickly reordered based on the "Ascending" selection in the drop-down, which invokes the AJAX call to re-sort the data without a server trip.

Here's what such a div might look like if I were to code the HTML.

<div id="famousPeople"> <table> <tr> <td> <ul> <li>Dave Thomas</li> <li>Ronald McDonald</li> <li>George Washington</li> </ul> </td> </tr> </table> </div>

This div markup, however, is the generated HTML—to avoid having to repeatedly create this snippet I'd much prefer to use a Tile to make the whole block easy to reuse and less prone to error. Something like this would be preferable:

<div id="famousPeople"> <tiles:insert definition="famousPeopleList" flush="true"> <tiles:put name="listbean" beanName="famousFolkForm" beanProperty="famousFolkList" /> </tiles:insert> </div>

In the sample code there is a cached list of these users in a session attribute called "famousFolks." This list will be resorted by the servlet that Dojo invokes using a standard Struts mapping to the URL (shown below).

<action path="/famousPeopleSort" name="famousFolkForm" scope="request" validate="false" type="com.tillman.dojo.action.FamousPersonageSort"> <forward name="success.sort" path="/sortUpdateXml.jsp"/> </action>

The newly sorted list is passed to sortUpdateXml.jsp. This JSP contains a <tiles:insert> and the Tiles context pulls in the list of names as it creates the markup returned in the AJAX (Dojo) response.

Let's fill in a little more functionality. The page that holds the AJAX call also includes a drop-down list field called sortBy that specifies an onchange event Javascript method call. The drop-down markup looks like the snippet below, but please check out the sample code for the full version.

<select name="sortBy" styleId="sortBy" onchange="javascript(doSort(this.value));"> <option name="person" value="George Washington">George Washington … </select>

When a new sort parameter is selected from the drop-down list the doSort( ) Javascript method defined in the <head> or included from a Javascript (.js) resource file is invoked. With this bit of context set, let's now take a look at the code for the Dojo call.

Dojo provides a varied and robust API that is generally speaking, intuitively packaged. With that said, it wasn't immediately apparent to me that an AJAX call is set up using a method from the dojo.io package —dojo.io.bind( ). (The 'io' must mean to and from the form. I would have preferred a .net package similar to Java's.)

function doSort(sortTerm) { dojo.io.bind( { url: "famousPeopleSort.shtml", content: {sortedBy: sortTerm}, method: "POST", mimetype: "text/html", load: function(type, value, evt) { processReturnValue(value); }, backButton: function(){ //for maintaining Dojo back button stack dojo.io.bind({ url: "famousPeopleSort.shtml", content: {sortedBy: previousSortTerm}, method: "POST", mimetype: "text/html", load: function(type, value, evt) { resetDropDown(previousSortTerm); processAjaxResponse(value); }, error: function(type, error) { alert("Error: " + error); } }); }, error: function(type, error) { alert("Error: " + error.reason); } });

This method call sets the URL argument for an HTTP Post to the servlet mapped to famousPeopleSort.shtml in the struts-config.xml (shown earlier or see the sample code). The content term holds the sortTerm argument, passed by the drop-down list onchange event call to the function, and sets it as a request parameter of the Post. The method argument mimics the method attribute of the HTML form tag and the mimetype specifies the type of data expected in the AJAX response. The load argument defines a function to be invoked when the AJAX response is returned.

You also may have guessed from the above Javascript method that Dojo can maintain expected browser 'back button' behavior; it does so by creating its own call stack. This is useful because, of course, many users navigate backward using the back button. In such circumstances, Dojo-invoked actions would be lost without a full page reload and the missing behavior might be confusing. Instead, each Dojo call is stacked on top of the previous one. As the user clicks the 'Back' button, the topmost, bound (meaning it was invoked by the io.bind method) call is popped off the stack and consumed.

The stack is managed with the help of an HTML file (iframe_history.html) that resides in the same directory as dojo.js. It is worth mentioning that while I've had success implementing Dojo's back button management features, other developers have reported some less pleasant experiences; if you encounter issues hopefully this anecdotal note will offer some comfort. ;-)

While it is great to have some programmatic control of the back button, if your business stakeholders aren't familiar with Dojo's 'back button' behavior you ought to prototype/demo it for them before you get too far into development. AJAX calls create a very different navigation and page flow paradigm from what might be "expected." Moreover, if something unknown (or that can't be seen) is happening that makes your site appear unresponsive, then users are usually unhappy and your application isn't likely to be successful. Therefore, you might want to consider adding a progress indicator or similar visual cue that starts up when the Dojo call is invoked and goes away when it is completed.

Before discussing how the returned data is processed I want to look at the response document that holds it. The response document for my famous personages sort is in XML format and acts as a container for the JSP tile referenced by the tile insert statement in the div described earlier.

<%@page contentType="text/xml" %> <%@taglib prefix="tiles" uri="/WEB-INF/tiles.tld" %> <%@taglib prefix="html" uri="/WEB-INF/struts-html.tld" %> <%@taglib prefix="bean" uri="/WEB-INF/struts-bean.tld" %> <ajax-response> <field id="famousFolk1" attribute="innerHTML"> <![CDATA[ <tiles:insert definition="famousPeople" flush="true" > <tiles:put name="famousFolkList" beanName="famousFolkForm" beanProperty="famousFolkList"/> </tiles:insert> ]]> </field> </ajax-response>

Because the tiles:insert is processed on the server, the Dojo call to the sorting action will rework the list and integrate it into the HTML created by the tile. When the AJAX call is made, this dynamically-generated HTML will be nested in the ajax-response XML element for processing by the Javascript function specified in the bind( ) method.

function processResponse(response) { if (djConfig["isDebug"]) { dojo.debug("ajax response: " + response); } //if the XML element ?ajax-response? isn?t found then write out what was returned if (response.toLowerCase().indexOf("<ajax-response>") < 0) { document.write(response); document.close(); return; } //parse the AJAX XML response into a document. var xmlDoc = dojo.dom.createDocumentFromText(response); //refer to the XML response above ? the ?field? element is inside //the response document. var fields = xmlDoc.getElementsByTagName("field"); for (var i = 0; i < fields.length; i++) { var id = fields[i].getAttribute("id"); var attribute = fields[i].getAttribute("attribute"); var value = null; if (fields[i].hasChildNodes()) { for (var j=0; j<fields[i].childNodes.length; j++) { var currentNode = fields[i].childNodes[j]; if(currentNode.nodeName.toLowerCase()=="#cdata-section") { value = currentNode.nodeValue; } } } //Dynamically builds a function called replaceValue and then invokes it eval("window.replaceValue = function(value) { dojo.byId('" + id + "')." + attribute + " = value;}"); replaceValue(value); } }

The processing of the response occurs during the parsing of the returned XML document. The code above finds the field element in the response and extracts its contents. It then replaces the div on the page with the specified #cdata-section value of the response. The replacement is an update to the DOM, of course, and the browser updates the display to reflect the change of content. This was the only cross browser parsing approach I could get working though I'm sure there are others out there.

Struts and Tiles have been key frameworks in enterprise Java development for years. AJAX and the Dojo toolkit are an excellent combination for using Struts and Tiles in a granular, service oriented manner that separates concerns and promotes the reuse of both code and display elements. While there is a learning curve to integrating these powerful frameworks, the payoff is worth the effort.



Doug Tillman is a veteran Java and Python developer turned Scrum Master.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap