Ajax programming with Struts 2

Five steps to dynamic tables using Struts 2, Dojo and JSON

1 2 3 4 5 Page 3
Page 3 of 5

Setting up the JSP page

Your server side is ready to go and it's time to prepare the display to render all of your gathered data into a table. The first thing you need to do is import the required Dojo components, as shown in Listing 2.

Listing 2. Initializing required Dojo components

<script type="text/javascript">
dojo.require("dojo.rpc.*");
dojo.require("dojo.widget.*");
dojo.require("dojo.widget.FilteringTable");
dojo.hostenv.writeIncludes();
</script>

Dojo's FilteringTable component is capable of displaying data in JSON (JavaScript Object Notation) format. Of course its look and feel is completely customizable with CSS. Your table definition should include headers only, with the field property set to the property to display, as shown in Listing 3.

Listing 3. Table definition

<table class="filterable" dojoType="filteringTable" id="yourTableId"
multiple="false" alternateRows="true" valueField="id">

<thead>
<tr>
<th field="id" dataType="String">ID</th>
<th field="firstName" dataType="String">First Name</th>

<th field="secondName" dataType="String">Second Name</th>
</tr>

</thead>
</table>

Note that FilteringTable was created to filter and sort data on the client side. You'll need to disable this functionality in order to proceed with filtering and sorting on the server side, as shown here:

var yourTable = dojo.widget.byId('yourTableId'); 
yourTable.createSorter = function() { return null;};

You now have two pieces of information (first name and second name) on the client side formatted as JSON strings. It's a good time to think about how you will transport the data between server and client.

Using Dojo and JSON for data transport

Many JavaScript-to-Java RPC solutions are available, most notably DWR. Struts 2 actually cuts out the need to for third-party libraries because it comes bundled with Dojo, which has RPC built in. What's more, the JSON plugin for Struts 2 automatically converts JSON-formatted strings to Java objects.

Preparing for JSON RPC calls

You need to do two things to expose your server-side Action methods to JavaScript on the client. The first is to edit your Struts 2 configuration XML file, as shown in Listing 4.

Listing 4. Struts 2 configuration for RPC calls

<package name="RPC" namespace="/nodecorate" extends="json-default">
<action name="SomeListRpc" class="some.ActionClass"
method="execute">
<interceptor-ref
name="json">

<param name="enableSMD">true</param>

</interceptor-ref>
<result type="json">
<param name="enableSMD">true</param>
</result>
</action>

</package>

The second thing you need to do is mark the ActionClass methods to be exposed as SMD (Standard Method Description format), as shown here:

@SMDMethod public List<Some> getRows(SortedPagedFilter filter)
{...}
@SMDMethod public long getRowCount(SortedPagedFilter filter) {...} 

Calling a Java method from JavaScript

Calling a Java method from JavaScript is as easy as calling a JavaScript function, but you need to have JsonService defined on the page:

<s:url id="smdUrl" namespace="/nodecorate" action="SomeListRpc"/>
var service = new dojo.rpc.JsonService("${smdUrl}");

With the JSON plugin, passing a parameter to a Java method is as easy as creating a JSON string and passing it into a JavaScript function. Dojo RPC calls a callback function once the return value is received from the Java method, and the JSON plugin transparently converts the return value of any type (including collections) into a JSON string. Of course, if an object hierarchy exists it converts the whole hierarchy of objects.

If you had a currentPage JavaScript variable on a page, the JavaScript code to pass a filter to getRows and get a JSON-formatted response would look like what you see in Listing 5.

Listing 5. Preparing contextual info and calling getRows and getRowCount

// define a callback function that will be called once a response is
//received
var rowsCallback = function(bean) {
var yourTable = dojo.widget.byId('yourTableId');
yourTable.store.setData(bean);
};
// construct a filter based on the current FilteringTable sort information
var sortInfo = yourTable.sortInformation[0];
var filter = {field:yourTable
.columns[sortInfo.index].field,direction:sortInfo.direction,page:current
Page};
// add criteria to the filter, dynamically construct your own one
// instead of the hardcoded one below
filter['criteria'] = {firstName:'Ivan',secondName:'Smith'};
var deferred = service.getRows(filter);
// start the RPC process
deferred.addCallback(rowsCallback);

var rowNumCallback = function(rowNumber) {
totalPages = Math.ceil(rowNumber / 20);
// call your fillPagination function rendering pagination info
fillPagination(totalPages);
}
// you don't need sort and page information to count rows, just filtering
//criteria
var filter = {field:'',direction:1,page:0};
filter['criteria'] = {firstName:'Ivan',secondName:'Smith'};
var deferred = service.getRowCount(filter);
// start the RPC process
deferred.addCallback(rowNumCallback);

In this case getRows returns a list of entities, which are Java beans. Sometimes you don't want to transfer all of your bean properties to the client side, however. For example, when a table has fewer columns than the bean has, or when you are transferring a Hibernate entity but you don't want to receive the service fields Hibernate attaches to its entities. You can control all this by adding the next line to the result part of the original action configuration shown in Listing 4. Keep in mind that the comma-separated strings are Java regex patterns.

<param name="excludeProperties">prop1,moreProp</param>


1 2 3 4 5 Page 3
Page 3 of 5