Most Java™ developers these days appreciate a good mashup, so you might be wondering how well Seam integrates with the technologies dubbed Web 2.0, especially Ajax. It would be pretty cool if you could use Seam to enable partial page updates in JSF or to facilitate a JSF application mashup with Google Maps, wouldn't it? Well, you can -- and better yet, it's easy.
In this final article in the
Seamless JSF series, I show you how to use the Seam
Remoting API and Ajax4jsf components to facilitate Ajax-style interactions in
JSF-based applications. As you'll learn, the best thing about combining Seam
with Ajax is that it lets you do all the fancy Web 2.0 stuff without
getting tangled up in the JavaScript XMLHttpRequest
object. With Seam Remoting and Ajax4jsf, you can communicate with managed
beans on the server as if they were local to the browser. The browser and
server state remain in sync, and you never have to deal with the low-level API
that facilitates their communication.
I'll begin by showing you how Seam facilitates a new, component-based approach to Ajax programming. You will learn to use the Seam Remoting API to communicate between JavaScript and server-side objects via Ajax. Once you understand this new (and easier) approach to Ajax, you can use it to enhance the Open 18 application by doing the following:
- Creating a mashup between the Open 18 course directory and Google Maps.
- Using Ajax4jsf to merge the application's course directory page and course detail page.
- Revisiting the application's Spring integration and making the Spring bean available during the Seam Remoting life cycle.
The mashup between Open 18 and Google Maps enables users to plot locations in the golf course directory on a map. Merging the course directory and course detail pages (and Ajaxifying the underlying code) allows you to show course details without loading a new page. Integrating your Spring bean with Seam Remoting enables you to capture a repositioning of the Google Maps location marker and store the corresponding course latitude and longitude points in your database. The result, as you'll see, is an impressive Web 2.0-style application that any golf player would love to use!
If you've been burned by over-complicated, JavaScript-heavy Ajax programming in the past or if you've simply avoided Ajax so far because you don't want to deal with its complexity, what this article teaches you should vanquish your fears. You do some JavaScript coding as you refactor the application, but unlike most Ajax implementations, JavaScript won't be the bulk of your code; instead it merely extends your server-side Java objects.
Just as you want to avoid explicit memory management in your applications, you do not want to have to deal with the low-level Ajax request protocols. Doing so is simply a big headache -- or multiple headaches, rather, with names like multibrowser support, data marshaling, concurrency violation, server load, and custom servlets and servlet filters. The biggest headache of all, and the one you really want to avoid, is the unintended exposure of the stateless, request-response paradigm that a component-based framework like JSF is intended to hide.
The JSF life cycle encourages strong component-oriented design by shielding application code from the underlying servlet model. To retain this abstraction when working with Ajax, you can hand off the low-level grunt work to either Seam Remoting or Ajax4jsf. Both of these libraries take care of the plumbing required to fuse JSF components to the browser via Ajax interaction. Figure 1 shows Seam Remoting in action. When an event is triggered, such as when a user clicks on a button, a message can be sent to the component on the server asynchronously. When the response is received, it can be used to incrementally update the page. The low-level communication protocols used to facilitate interaction between the browser and the server-side component are hidden behind the API.
In the use case shown in Figure 1, the user is presented with the results of the method call that happens after he or she clicks a button. There are two important points to keep in mind when studying this use case: (1) the page is never refreshed, and (2) the client code communicates transparently with the method on the component, rather than explicitly building and then requesting a URL. A standard HTTP request is being used behind the scenes, of course, but the client code never has to interact with the HTTP protocol directly.
Figure 1. Seam Remoting fuses JSF component and browser
Seam Remoting and Ajax4jsf are distinct libraries that each serve a purpose in the "Ajaxification" of JSF. Both libraries use Ajax to introduce an interaction model where communication between the browser and the server can happen asynchronously in the background, invisible to the user. After all, why waste the user's time with a page reload just to execute methods on the server? The information retrieved from the server in the Ajax requests made by these libraries can be used to incrementally update the state of the page in "real time." Both libraries instrument a life cycle that can restore the state of the components whenever they are needed by the browser. This Ajax interaction is not really a request but rather a "restore and execute." The browser is sort of tapping the server on the shoulder and asking it to execute a method on one of the server-side managed beans and return the result.
Although the two libraries work differently, they are not mutually exclusive. Because they both honor the JSF component model, they can easily be combined, as this article shows later. For now, just briefly consider each one's approach to incorporating Ajax-style interaction into JSF applications:
-
Seam Remoting provides JavaScript APIs that you can use to access
server-side components in JavaScript as if they were local objects, to either
send or retrieve data through method calls. Seam Remoting uses a custom, non-JSF
life cycle to allow the browser to communicate with the server-side components.
Only the Seam container and its components are restored during these requests.
The transport protocol is Ajax, but you do not need to worry about the details
of how the packets are transferred.
- Ajax4jsf pushes the abstraction even further by completely hiding the use of JavaScript. It wraps all of the logic inside of basic UI components. Ajax4jsf takes the Ajax request through the complete JSF life cycle. Therefore, the Ajax-enabled components can execute action handlers on the server, update the JSF component tree, and re-render portions of the page, all without triggering a browser-navigation event. Again, the communication is done through Ajax, but it all happens under the covers, invisible to the page developer. Ajax4jsf's component-oriented approach makes Ajax functionality a natural part of JSF rather than a non-conforming outlander.
I dig into both of these approaches, but let's get some Ajax basics out of the way first.
To make an application "rich" in the Ajax/Web 2.0 sense, the Web browser (otherwise known as the client) must be able to directly access components on the server. Making this vision a reality is challenging because of the rather significant chasm between client and server. On one side of this chasm (also known as the network) sits the client browser and on the other side sit the server and its components. The goal of an Ajax application is to get them talking to each other.
Actually, in most traditional Web applications, the client and server are already talking just fine, it's just that the interaction is completely one sided. The server talks, the browser listens. No doubt you've been stuck in those kinds of conversations before. In a world without Ajax communication, the browser can send a synchronous request for any URL, but it is then obliged to render whatever HTML the server sends back. Another disadvantage of this sort of interaction is that it often entails a lot of waiting.
With only the rudimentary language of HTTP in hand, the browser client remains completely naive about how the server generates HTML and therefore knows nothing of its components. From the standpoint of the browser, the page generation process is just a black box. The browser can ask the server different questions in the form of URLs and pass along hints packaged as request parameters and POST data, but it doesn't really speak the server's language. If you want to give the browser a view into your application's server-side activity, you need to establish a more sophisticated means of communication. The deterministic, page-oriented approach just doesn't cut it.
The Web browser as Ajax client
Seam Remoting and Ajax4jsf take distinctly different approaches to breaking
the ice between client and browser components, so it's important to know how to
leverage each one. Seam Remoting offers an API in the browser's native tongue,
JavaScript, through which methods on server-side components can be accessed. For access to be granted to those methods, they must be explicitly marked
as "remote" through the @WebRemote annotation.
The invocation mechanism in Seam Remoting parallels Java RMI by similarly allowing JavaScript to call methods on a component located on a remote server, using a local proxy object, or "stub." As far as the client is concerned, this stub object is the remote object. The stub is responsible for carrying out method execution on the actual remote object. When a remote method is called, the response encapsulates the return value of the method call. The return value is unmarshaled as a JavaScript object as it comes back across the wire. Hence, Seam Remoting enables the browser to speak the native tongue of the server. Java code and JavaScript have at last become one -- which may surprise those who still believe that they have been the same language all along!
Ajax4jsf, on the other hand, provides JSF component tags for declaratively
associating UI events with action handlers on server-side managed beans. These
action handler methods do not need to be marked as "remote." Instead,
they are traditional JSF action handlers, which are public methods on
managed beans that either have no-arguments or accept an ActionEvent.
Unlike Seam Remoting, Ajax4jsf can communicate changes in the JSF component
tree back to the browser. These changes are returned in the form of XHTML
fragments. The fragments correlate to the individual JSF components on the
page and manifest themselves as partial page updates. Thus, isolated areas of
the page can be re-rendered by the browser using the new markup. These
fragments are specifically requested, either by using the reRender attribute of the Ajax4jsf component tag or by
wrapping regions of a template in an Ajax output panel marked with the
attribute ajaxRendered="true". The reRender attribute indicates a specific set of components
that should be re-rendered, referenced by their component IDs. In contrast,
the use of ajaxRendered="true" is a blanket
approach, instructing all "Ajax rendered" regions to be updated whenever an
Ajax4jsf-managed Ajax request completes.
Both Ajax4jsf and Seam Remoting ripen the browser from a basic HTML renderer
to a fully mature Ajax client. Applications really get exciting when you
integrate these two frameworks. In fact (this next point could surprise you, so
you might want to sit down for it), combining the capabilities of Seam Remoting
and Ajax4jsf can free you from developing your own custom Ajax JSF components!
You can enlist any existing, non-Ajax enabled JSF component in Ajax
communication simply by nesting an a4j:support tag
inside of its declaration. If you are working outside of a UI component (as you do later in the Google Maps mashup) and need to query a server-side
component for information, update it, or instruct it to perform an operation,
you can use Seam Remoting to manage the interaction.
While Seam Remoting and Ajax4jsf do have some functional overlap, both come in handy for adding Ajax-style interactions to your applications. Furthermore, as you'll soon see, the two libraries provide a seamless Ajax solution for your JSF applications.
You may be thinking that Seam Remoting sounds like an ideal solution if only it weren't so complex to implement. Fear not! Seam Remoting is nothing like those scary remote EJB objects you're probably thinking of. The best part about using the Seam Remoting API to enable JavaScript code to interact with server-side components is that the process is so unbelievably simple. Seam truly does all the hard work for you -- you don't even have to edit a single line of XML to start using it! (Which should be a relief if you're doing more XML programming than Java programming these days.)
Let's take a quick walk through the steps involved in using Seam Remoting to "Ajaxify" your JSF applications.
There are just two requirements for exposing a server-side object method to
a remote Ajax client: the method must be a public member of a Seam component
and it must be outfitted with the @WebRemote
annotation. That's it!
You can see this simplicity in action in Listing 1, where the Seam component
ReasonPotAction exposes a single method, drawReason(), to an Ajax client for remote execution.
Each time this method is invoked on the stub, the call is passed across the
Internet to the server and randomly selects one of the listed "10 reasons to use
Seam for your next project," using the corresponding method on the server-side
component. The server then returns that value back across the wire to the
client. (See Resources to find out more about those 10
reasons.)
Listing 1. Exposing a remote method call
@Name("reasonPot")
@Scope(ScopeType.SESSION)
public class ReasonPotAction {
private static String[] reasons = new String[] {
"It's the quickest way to get \"rich\".",
"It's the easiest way to get started with EJB 3.0.",
"It's the best way to leverage JSF.",
"It's the easiest way to do BPM.",
"But CRUD is easy too!",
"It makes persistence a breeze.",
"Use annotations (instead of XML).",
"Get hip to automated integration testing.",
"Marry open source with open standards.",
"It just works!"
};
private Random randomIndexSelector = new Random();
@WebRemote
public String drawReason() {
return reasons[randomIndexSelector.nextInt(reasons.length)];
}
}
|
With the server-side component set up, you need to prepare the browser
to invoke the @WebRemote method. Seam uses a custom
servlet to handle HTTP requests to execute a remote method and return its
result. No worries, though: You will not have to interact with that servlet
directly. The Seam Remoting JavaScript library takes care of all interactions
with the Seam servlet in the same way it manages all aspects of the XMLHttpRequest object.
The only time you even have to think about this servlet is when you are
setting up Seam Remoting in your application. Better yet, you only need to
configure Seam's custom servlet once, regardless of how many of your Seam features require the services of a custom servlet. Instead of using a
specialized servlet for each feature that serves an asset to the browser (such
as a JavaScript file) or processes a non-JSF request (like an Ajax remoting
calls), Seam bundles these duties under the umbrella of a single controller,
the Resource Servlet. This servlet uses a delegating chain model, passing on the duties to registered handlers. For instance, the Remoting object (which I discuss later) registers
itself to receive all Ajax requests sent by the Seam Remoting JavaScript
library.
The XML definition for the Resource Servlet, shown in Listing 2, must be installed in your application's web.xml file, typically below the Faces Servlet:
Listing 2. The XML definition for the Seam Resource Servlet
<servlet>
<servlet-name>Seam Resource Servlet</servlet-name>
<servlet-class>org.jboss.seam.servlet.ResourceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Seam Resource Servlet</servlet-name>
<url-pattern>/seam/resource/*</url-pattern>
</servlet-mapping>
|
The real magic behind the Seam Remoting mechanism occurs in the JavaScript
libraries. The Resource Servlet delegates work to the Remoting object to serve these libraries. The emitted
JavaScript libraries, of which there are two, make the appropriate hooks
available to the browser for invoking the remote methods via JavaScript.
You have to import both of the JavaScript libraries shown in Listing 3
into your page. The first library, remote.js, is static and ships Seam's
client-side remoting framework to the browser. The second library,
interface.js, is created dynamically on each request. It contains the remote
stubs and complex types that are needed to interact with your server-side
components. Note that Seam does not generate stubs and complex types for all
components that specify a method as remote. Instead, it parses the query
string following the ? symbol in the interface.js
URL to collect the names of components to expose. Each component name is
separated by an & symbol. Using this
stacked approach keeps the number of requests by the browser for external
JavaScript files to a minimum. Seam then generates the stubs and complex types
for only those components. As an example, the two JavaScript imports in
Listing 3 load the Seam Remoting library and instruct Seam to prepare both
the reasonPot and the anotherName
component:
Listing 3. Importing the client-side framework and APIs
<script type="text/javascript"
src="seam/resource/remoting/resource/remote.js"></script>
<script type="text/javascript"
src="seam/resource/remoting/interface.js?reasonPot&anotherName"></script>
|
That's it -- you are ready to go! You've completed the Seam Remoting setup and it's time to start making some remote calls.
All the client-side framework code for the Seam Remoting library is
encapsulated in the Seam JavaScript object.
Actually, this top-level object is simply a namespace -- a container that
allows you to bundle a set of functionality under a unique name. In
JavaScript, a namespace is nothing more than an object that holds static
properties, static methods, and other nested namespace objects. The
important thing to note is that the JavaScript in the Seam Remoting
library plays well with other JavaScript libraries. That means that you can
leverage libraries like Prototype or Dojo to allow the JSF UI to interact with
your server-side components. The flexibility to use third-party JavaScript
libraries in a JSF UI has been a huge challenge for JSF developers in the past,
so the Seam JavaScript object is a
welcome addition.
The functionality of the Seam Remoting library is divided between the Seam.Component and Seam.Remoting namespace objects. The static methods of
Seam.Component provide access to remote Seam
components, while the static Seam.Remoting methods
are used to control the remoting settings and to create custom data types. As
you will see later in the example, a custom data type is any non-primitive
object that you need to create locally to call a remote method.
To execute a remote method, you first have to obtain an instance of the
component that hosts the method. You can use Seam.Component.getInstance() if you want to use an
existing Seam component instance (meaning it exists on the server) or Seam.Component.newInstance() if you want to instantiate a
new instance before making the call. These instances are actually remote stubs
that are built dynamically by Seam. The stub proxies all method calls,
marshaling the component name, method, and arguments into XML, and passes the
XML payload over the wire to the server-side component using an Ajax request.
The remoting framework code on the server picks up this request, unmarshals
the XML, extracts the component name, method, and arguments, then invokes the
method on the server-side component instance. Finally, the server sends the
return value back to the client in the response of the Ajax call. Again, all
this work is hidden behind the stub, greatly simplifying your JavaScript
code.
That's enough background talk for now. Listing 4 shows how you would execute
the drawReason() method on the session-scoped reasonPot component, which was shown flagged as a remote
service back in Listing 1. Once the component stub is
obtained, executing the method looks like any other JavaScript method call ...
or does it?
Listing 4. Invoking a remote method
<script type="text/javascript">
Seam.Component.getInstance("reasonPot").drawReason(displayReason);
function displayReason(reason) {
alert(reason);
}
</script>
|
In Listing 4, you should notice that there is an important difference between the method on the stub and the "real" method on the server-side component. Can you tell what it is? Consider that every call made on a component stub is done asynchronously. That means the result of the remote method call is not immediately available to the executing thread. As such, the method on the stub does not have a value to return, regardless of whether the matching server-side method has one. When a method on the remote stub is executed, it is effectively saying, "Umm, yeah, I'll get back to you on that. Do you have a number where I can reach you?" The number you provide is a "callback" JavaScript function. Its job is to capture the actual return value.
The callback function is a standard construct in asynchronous APIs. It is executed by the Seam Remoting JavaScript framework when a response from an Ajax request arrives back at the browser. If the method on the server-side component has a non-void return value, that value is passed into the callback function as its only argument. On the other hand, if the method on the server-side component has a void return type, then you can disregard this optional parameter.
For most of your remoting needs, you can get by with the APIs described so far. When a request is made to retrieve a server-side component or execute one of its exposed methods, that component must be visible within the scope of the remoting life cycle. Referencing session-scoped beans requires no extra work because they are always available to an HTTP request made within the same browser session.
Conversation-scoped beans are trickier, though, because they are associated with an HTTP request based on the presence of the conversation token. When you're working with conversation-scoped beans, you need to be able to establish the correct conversation context during the remote call. The conversation is reactivated by sending the conversation token along with the remote request. The details of this interaction are handled by the Seam Remoting framework, so long as you provide the conversation token to send.
Keep in mind that it is entirely possible for Ajax requests made from the
same window to communicate with two or more distinct conversations on the
server. Seam can differentiate between them if you specify the conversation
ID before retrieving the component instance stub. You accomplish this by using
Seam.Remoting.getContext().setConversationId("#{conversation.id}
").
Seam always exposes the current conversation ID under the #{conversation.id} value-binding expression. JavaScript
actually sees the resolved value, typically a number, rather than the
expression placeholder. You are effectively registering this value with the
remoting context, which is then propagated along with the requests that
follow. Conversely, you can read the conversation ID assigned during the
previous request by calling Seam.Remoting.getContext().getConversationId() after a
remote call has been made. This way, your remoting calls can leverage the
benefits of the conversation scope and participate in stateful
behavior.
Google mapping the Open 18 application
The Reason Pot example in Listing 1 ("Ten good reasons to use Seam") was fun, but now it's time to make the Seam Remoting library do some real work! Shift your focus back to the Open 18 application introduced in "Seamless JSF, Part 2: Conversations with Seam." As you probably recall, Open 18 is a golf course directory. Users can browse the list of courses and then drill down to view the details of a single course. Open 18 also allows the user to create, update, and delete courses. In its original Web 1.0 incarnation, every interaction the user had with this application led to a page being reloaded.
There are various ways to enhance the Open 18 application with the magic of Ajax, and you'll have the opportunity to try a few of them before we're done. The first thing you can do is to create a mashup between Open 18 and Google Maps. You can't travel far on the Internet these days without running into a mapping implementation, and your users will certainly appreciate the addition of a mapping feature in Open 18. Combining the Seam Remoting API and the Google Maps Geocoder API allows you to plot the location of each course in your coarse directory on a Google map, which appears just below the directory listing.
Geo-spatial mapping might sound tricky, but not when the Google Maps
JavaScript API does so much of the work for you. The GMap2 class draws the map and responds appropriately to
scrolling and zooming events in the viewport. Another Google Maps class, GClientGeocoder, resolves geo-spatial coordinates based on
an address string. Your job is simply to initialize the map and add the
markers for each course.
To place the markers onto the map, you first fetch
the collection of courses from the server-side component through a remote method
call. Next, you translate the address of each course into a geo-spatial point
(longitude and latitude) using the GClientGeocoder.
Finally, you use that point to place a marker on the map at the corresponding
coordinates. As an extra little feature, you can outfit each row in the directory listing
with a compass icon that sits next to the edit icon. When the compass icon is clicked in
one of the directory rows, the map pans and zooms until the selected course comes into
view. At the same time, the map also renders a balloon above the marker that displays the
name, address, phone number, and Web site of a given course. This same balloon can be brought up by clicking one of the markers on the map directly. Figure 2 is a preview of what the application will look like when it is all done:
Figure 2. A screenshot of the Google Maps mashup
While the integration of a map component with location-oriented data is interesting in general, this mashup is especially revealing. Users actually are able to view the boundaries of each course on the map! In Map mode, the golf course property is rendered in light green. If the zoom is great enough, a label overlay appears on the course area revealing the name of the course. Even more interesting is Satellite mode, which reveals the course landscape. With enough zoom, it is possible to make out the individual features of each hole, such as the tee box, fairway, and green! This mashup of golf course locations and an interactive map view offers quite a reward for your effort!
Google Maps is very easy to integrate and embed into a Web application. As
I mentioned already, it takes care of all the details of rendering the map and
dually provides an API for plotting geo-spatial locations on that map. The
GClientGeocoder object does the tough work of
resolving the geo-spatial location and handing back the latitude and longitude
data you need.
The method stub that translates addresses to geo-spatial points works similarly to the Seam Remoting method stubs. A callback function is passed to the method when it is invoked to capture the return value. At that time, an Ajax request is sent off to Google HQ and the callback function is executed when the response arrives back at the browser. The intelligent interface of the Google Maps API makes it possible to plot locations very liberally based merely on a postal address. It's a good thing that you do not have to worry about maintaining the geo-spatial coordinates for each course because that would be a real chore! Additional routines on this API then use the latitude and longitude data to construct and render markers for these locations on the map.
Two API methods are relevant for the map integration: Geocoder.getLatLng() and GMap.addOverlay(). First, the Geocoder.getLatLng() method resolves an address string to
a GLatLng point. This data object merely wraps a
pair of latitude and longitude values. The second argument to this method is a
callback JavaScript function that executes once the communication with Google
HQ is complete. This function proceeds to add a marker overlay onto the map
using GMap.addOverlay(). By default, a marker is
rendered as a red pin. The tip of the pin points to the address location on
the map.
Listing 5 shows the JavaScript code for setting up your Google map and adding markers to it. The functions are listed in order of execution. You should recognize the Seam Remoting scripts that were used in Listing 3, in addition to the new import of the Google Maps API script.
Listing 5. Mapping Open 18 with Geocoder
<script type="text/javascript"
src="http://maps.google.com/maps?file=api&v=2.x&key=GOOGLE_KEY"></script>
<script type="text/javascript"
src="seam/resource/remoting/resource/remote.js"></script>
<script type="text/javascript"
src="seam/resource/remoting/interface.js?courseAction"></script>
<script type="text/javascript">
// <![CDATA[
var gmap = null;
var geocoder = null;
var markers = {};
var mapIsInitialized = false;
GEvent.addDomListener(window, 'load', initializeMap);
/**
* Create a new GMap2 Google map and add markers (pins) for each of the
* courses.
*/
function initializeMap() {
if (!GBrowserIsCompatible()) return;
gmap = new GMap2(document.getElementById('map'));
gmap.addControl(new GLargeMapControl());
gmap.addControl(new GMapTypeControl());
// center on the U.S. (Lebanon, Kansas)
gmap.setCenter(new GLatLng(38.2, -95), 4);
geocoder = new GClientGeocoder();
GEvent.addDomListener(window, 'unload', GUnload);
addCourseMarkers();
}
/**
* Retrieve the collection of courses from the server and add corresponding
* markers to the map.
*/
function addCourseMarkers() {
function onResult(courses) {
for (var i = 0, len = courses.length; i < len; i++) {
addCourseMarker(courses[i]);
}
mapIsInitialized = true;
}
Seam.Remoting.getContext().setConversationId("#{conversation.id}");
Seam.Component.getInstance("courseAction").getCourses(onResult);
}
/**
* Resolve the coordinates of the course to a GLatLng point and adds a marker
* at that location.
*/
function addCourseMarker(course) {
var address = course.getAddress();
var addressAsString = [
address.getStreet(),
address.getCity(),
address.getState(),
address.getPostalCode()
].join(" ");
geocoder.getLatLng(addressAsString, function(latlng) {
createAndPlaceMarker(course, latlng);
});
}
/**
* Instantiate a new GMarker, add it to the map as an overlay, and register
* events.
*/
function createAndPlaceMarker(course, latlng) {
// skip adding marker if no address is found
if (!latlng) return;
var marker = new GMarker(latlng);
// hide the course directly on the marker
marker.courseBean = course;
markers[course.getId()] = marker;
gmap.addOverlay(marker);
function showDetailBalloon() {
showCourseInfoBalloon(this);
}
GEvent.addListener(marker, 'click', showDetailBalloon);
}
/**
* Display the details of the course in a balloon caption for the specified
* marker. You should definitely escape the data to prevent XSS!
*/
function showCourseInfoBalloon(marker) {
var course = marker.courseBean;
var address = course.getAddress();
var content = '<strong>' + course.getName() + '</strong>';
content += '<br />';
content += address.getStreet();
content += '<br />';
content += address.getCity() + ', ' +
address.getState() + ' ' +
address.getPostalCode();
content += '<br />';
content += course.getPhoneNumber();
if (course.getUri() != null) {
content += '<br />';
content += '<a href="' + course.getUri() + '" target="_blank">' +
course.getUri().replace('http://', '') + '</a></div>';
}
marker.openInfoWindowHtml(content);
}
// ]]>
</script>
|
While Listing 5 appears a bit daunting at first, there really isn't much to
it. When the page loads, the Google map is instantiated using the GMap2 constructor, inserted into the target DOM element,
and centered on the United States. Et Voila! -- you have a
Google Maps display. Easier than you thought, right? Once the map is
initialized, the course markers are added. The crux of the code is creating
those markers, so let's focus on the addCourseMarkers() function, where the Seam Remoting API
is tapped.
To save on server resources, you want your remote call to retrieve the same
list of courses that are loaded into the conversation scope while the page is
being rendered by JSF. To ensure that the conversation that holds this
collection is reactivated during the remote call, the conversation ID must be
established on the remoting context, as discussed earlier. To review, the
value binding expression that references the current conversation ID, #{conversation.id}, is resolved when the page renders, and
its value is then passed to the Remoting Context via the setConversationId() method. Any subsequent calls to a
remote method through a component stub will pass along this value and activate the
associated conversation. With the conversation active, the same list of courses
are available.
To pull down the courses, the next step is to grab a reference to the
component named courseAction, using Seam.Remoting.getInstance(), and then execute the getCourses() method on that stub. As explained earlier,
the stub method takes a callback function as its final argument, which is used to capture the list of courses in the response. Notice that the return value is
no longer a primitive type but rather a collection of user-defined JavaBeans.
Seam creates JavaScript structures to emulate any JavaBean classes that are
used in the method signatures of the server-side components. In this case,
Seam unmarshals the response to JavaScript objects that represent Course entities, with all the same getters and setters.
The getter methods on the course object are used in various places in Listing 5 to read the course data.
In the final step of placing the courses on the map, the address for each
course is converted into a GLatLng point using the
GClientGeocoder. That value is then used to create
a GMarker widget that is added as an overlay on the
map.
Now your map is decorated with all these fancy markers, but there is no way to associate the rows in the course directory with the markers on the map. This is where the compass icon comes into play. You are going to add a little bit more JavaScript that uses the Google Maps API to zoom and center the map on the course when the compass icon for that course is clicked. Listing 6 shows the component tag for the compass icon. This component is inserted in each row in the directory, just before the edit icon. (The functionality of editing a course is described in "Seamless JSF, Part 2: Conversations with Seam.")
Listing 6. The component tag that renders the compass icon
<h:graphicImage value="/images/compass.png" alt="[ Zoom ]" title="Zoom to course on map"
onclick="focusMarker(#{_course.id}, true);" />
|
The JavaScript event handler, focusMarker(), is
registered to handle the onclick event on the compass icon. The focusMarker() method, shown in Listing 7, looks up the
GMarker previously registered for that course in a
global registry and then delegates the work to the showCourseInfoBalloon() function from Listing 5:
Listing 7. The focusMarker() event handler focuses the map on the selected course
/**
* Bring the marker for the given course into view and display the
* details in a balloon. This method is registered in an onclick
* handler on the compass icons in each row in the course directory.
*/
function focusMarker(courseId, zoom) {
if (!GBrowserIsCompatible()) return;
if (!mapIsInitialized) {
alert("The map is still being initialized. Please wait a moment and try again.");
return;
}
var marker = markers[courseId];
if (!marker) {
alert("There is no corresponding marker for the course selected.");
return;
}
showCourseInfoBalloon(marker);
if (zoom) {
gmap.setZoom(13);
}
}
|
Listing 8 reveals the method on the server-side component that is responsible for exposing the previously fetched courses. (The CRUD action handlers created for the Open 18 implementation in "Seamless JSF, Part 2: Conversations with Seam" are excluded from this listing for brevity.)
Listing 8. The CourseAction component exposes the getCourses method
@Name("courseAction")
@Scope(ScopeType.CONVERSATION)
public class CourseAction implements Serializable {
/**
* During a remote call, the FacesContext is <code>null</code>.
* Therefore, you cannot resolve this Spring bean using the
* delegating variable resolver. Hence, the required flag tells
* Seam not to complain.
*/
@In(value="#{courseManager}", required=false)
private GenericManager<Course, Long> courseManager;
@DataModel
private List<Course> courses;
@DataModelSelection
@In(value="course", required=false)
@Out(value="course", required=false)
private Course selectedCourse;
@WebRemote
public List<Course> getCourses() {
return courses;
}
@Begin(join=true)
@Factory("courses")
public void findCourses() {
System.out.println("Retrieving courses...");
courses = courseManager.getAll();
}
// .. additional CRUD action handlers ..
} |
As explained earlier, the method stub can include an optional callback
JavaScript function as its last argument. That is why the getCourses() object on the client-side stub takes a single
argument, while the corresponding method on the server-side component takes
none. The getCourses() method returns the
collection of courses present at the time when the page is rendered.
By now, you may be wondering how all these Seam Remoting requests
play in with the JSF life cycle. Well, the fact is they don't. At least not in
the typical way. Not awakening the JSF life cycle is what keeps the requests so
lightweight. When a method is invoked on a component stub from the JavaScript,
the call is channeled through the Seam Resource Servlet and handled by the
Remoting delegate object. Because the Resource
Servlet does not take requests through the JSF life cycle, the
FacesContext is not available during the remote call. Instead, the
Resource Servlet uses its own life cycle that merely taps into the Seam
component container. Bottom line: Any value-binding expression that does not
resolve to an object managed by Seam will be null
when a method is executed through the Seam Remoting API. Additionally, JSF
component bindings used by the backing bean will not be available either.
In the initial configuration of the Open 18 application, the CourseAction bean relies on the Spring framework's JSF
variable resolver to inject a CourseManager object
into the backing bean through the value-binding expression #{courseManager}. The trouble is, the Spring variable
resolver relies on the FacesContext to locate the Spring container. If the
FacesContext isn't available, then the variable resolver can't
access any of the Spring beans. Thus, the value-binding expression #{courseManager} will be null
when the component is accessed via the Seam Remoting API.
This situation presents a challenge if you need to use the CourseManager object for anything during a remote method
call. Because the default behavior of Seam is to enforce the existence of
dependencies, you need to mark the @In annotation
with the attribute required=false, which indicates
that it is optional. Doing this keeps Seam from complaining when a call is
made through a remote stub.
The next sections shows how to use Ajax4jsf to merge your
application's course directory page and course detail page. This enhancement
does require the service layer object to persist the changes to a
course. However, because Ajax4jsf uses the complete life cycle when it invokes
action handlers, the #{courseManager} expression resolves in the normal way.
The Google Maps mashup is very exciting, but adding it to the Open 18 application introduces a small design problem. When the user clicks on a course name in the directory, to view detailed information about that course, the page refreshes. As a result, the map is frequently reloaded. These reloads add way too much overhead to both the browser and the server and restore the map to its initial position.
To prevent the map from reloading, you must restrict all activity
to a single page load. Unfortunately, the process of submitting a form is tied
to a page request. This tight coupling is default behavior for Web browsers.
What you need is for the data to be sent to the server without the browser
requesting the page again -- which is precisely the challenge the Ajax4jsf component library was designed to resolve. Swapping out the h:commandLink tag with the a4j:commandLink tag in the Name column of the directory
listing enables Ajax4jsf to work its magic whenever a course link is clicked.
When the Ajax4jsf version of the link is activated, it executes the method
indicated by the method-binding expression in the action attribute, #{courseAction.selectCourseNoNav}, and then returns
replacement markup containing the course details to be inserted below the
directory.
The selectCourseNoNav() method performs the same
logic as the selectCourse() method but it has no
return value to ensure that JSF will not pursue a navigation event. After all,
the whole point of the Ajax request is to prevent the browser from requesting
the page again. The XHTML markup returned is inserted in the area below
the map without disrupting the map's state. The component shown in Listing 9
replaces the h:commandLink used in the original
version of courses.jspx, found in "Seamless JSF,
Part 2: Conversations with Seam":
Listing 9. The Ajax4jsf-managed link for selecting a course
<a4j:commandLink id="selectCourse"
action="#{courseAction.selectCourseNoNav}" value="#{_course.name}" />
|
As mentioned earlier, there are two ways to indicate which areas of the
page to incrementally update. One way is to target individual components by
specifying their component IDs in the reRender
attribute of the Ajax4jsf action component. The other way is to wrap regions
of the page in an a4j:outputPanel tag and mark
those regions as "Ajax rendered" using the ajaxRendered attribute on
that tag. Listing 10 shows the region of the view template that outputs the
details of a selected course using the output-panel approach:
Listing 10. The Ajax-rendered course details panel
<a4j:outputPanel id="detailPanel" ajaxRendered="true">
<h:panelGroup id="detail" rendered="#{course.id gt 0}">
<h3>Course Detail</h3>
<!-- table excluded for brevity -->
</h:panelGroup>
</a4j:outputPanel>
|
Before you can run the updated code, you need to configure Ajax4jsf in your application. First, add the jboss-ajax4jsf.jar library and its dependency, oscache.jar, to your application's classpath. Second, add the Ajax4jsf filter to your web.xml. The definition for this filter is shown in Listing 11. Note that the Ajax4jsf filter must be the first filter defined in the web.xml file. Much like Seam's Resource Servlet, this filter handles remote calls initiated by Ajax4jsf components. Unlike Seam's remote calls, however, the JSF life cycle is active during an asynchronous Ajax4jsf request, hence the need for a servlet filter rather than a regular servlet. You also need to shift the configuration of the Facelets view handler from the faces-config.xml file to the web.xml, where it is defined in a servlet context parameter.
Listing 11. Configuring Ajax4jsf
<context-param>
<param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
<param-value>org.jboss.seam.ui.facelet.SeamFaceletViewHandler</param-value>
</context-param>
<filter>
<filter-name>Ajax4jsf Filter</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>Ajax4jsf Filter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
|
Once you have included the libraries and installed the markup in Listing 11 in the web.xml file, you are ready to use Ajax4jsf! Just be sure to
add the Ajax4jsf tag library definition, xmlns:a4j="https://ajax4jsf.dev.java.net/ajax", to the top
of any view template in which you use the Ajax4jsf tags. Of course,
much more is possible with this library than I can cover in one article.
See Resources for more information and
examples related to using Ajax4jsf.
Up to this point, the Open 18-Google Maps mashup has been entirely read-only. Much of the excitement around Ajax is about being able to change back-end data without reloading the page. I think it's time to have a little fun with our Google map so that it sends some data back. Assume, for the sake of demonstration, that even though Google can resolve any postal address to a latitude and longitude coordinate, the user still wants to be able to specify a custom location for the pin. To be precise, let's say the user wants the pin to point to the clubhouse or the first tee of a given course. Fortunately, the Google Map API makes this final enhancement to the Open 18 application very easy to pin down.
You start by allowing the marker to be dragged. To turn this feature on,
simply add the draggable flag to GMarker
when it is instantiated. Next, register a JavaScript function that listens
for the "dragend" event that is fired when a user finishes dragging the
marker. Inside of this callback function, you execute a new method on the
courseAction stub, setCoursePoint(), to save this new point.
Listing 12 shows the modified createAndPlaceMarker function. The addCourseMarker() function is also modified to consider
the custom point on the Course entity when placing
the marker.
Listing 12. Updated JavaScript with marker dragging enabled
function addCourseMarker(course) {
var address = course.getAddress();
if (course.getPoint() != null) {
var point = course.getPoint();
var latlng = new GLatLng(point.getLatitude(), point.getLongitude());
createAndPlaceMarker(course, latlng);
}
else {
var addressAsString = [
address.getStreet(),
address.getCity(),
address.getState(),
address.getPostalCode()
].join(" ");
geocoder.getLatLng(addressAsString, function(latlng) {
createAndPlaceMarker(course, latlng);
});
}
}
function createAndPlaceMarker(course, latlng) {
// skip adding marker if no address is found
if (!latlng) return;
var marker = new GMarker(latlng, { draggable: true });
// hide the course directly on the marker
marker.courseBean = course;
markers[course.getId()] = marker;
gmap.addOverlay(marker);
function showDetailBalloon() {
showCourseInfoBalloon(this);
}
function assignPoint() {
var point = Seam.Remoting.createType("com.ibm.dw.open18.Point");
point.setLatitude(this.getPoint().lat());
point.setLongitude(this.getPoint().lng());
var courseActionStub = Seam.Component.getInstance("courseAction");
courseActionStub.setCoursePoint(this.courseBean.getId(), point);
}
GEvent.addListener(marker, 'click', showDetailBalloon);
GEvent.addListener(marker, 'dragstart', closeInfoBalloon);
GEvent.addListener(marker, 'dragend', assignPoint);
}
|
You're not done just yet, but you are so close! You still need to add a
method to the server-side component that saves this GLatLng
point. Bear with me, though, because you need to
revisit the Open 18 application's Spring integration before completing this
final feature of the mashup. (See "Seamless JSF,
Part 2: Conversations with Seam" to revisit the details of the Spring
integration.)
As discussed earlier, the variable-resolver approach I originally used to integrate the Spring container into Seam has its limitations. Frankly, it has been stretched to the point that it is beginning to tear, and it's time to bid it farewell. While most of Seam's features involve JSF, some aspects of Seam work outside of the JSF life cycle. To achieve true integration with Spring, you need a better solution than a custom variable resolver. Fortunately, Seam's developers have addressed this need by adding a Seam extension for Spring. The Spring integration package leverages a new feature in Spring 2.0 for creating schema-based extension points. These extensions enable the use of custom XML namespaces in the bean definition file and namespace handlers that operate on those tags during the Spring container startup process.
The Seam namespace handler for Spring (you may have to say that a few times
aloud before it makes sense) has several ways of interacting with the Spring
container. To eliminate the custom variable resolver, you need to
somehow expose a Spring bean as a Seam component. The seam:component tag is used for this purpose. Placing this
tag inside of a Spring bean declaration notifies Seam that the Spring bean
should be proxied, or wrapped, as a Seam component. At this point, the Seam
bijection mechanism treats the bean just as if it had been annotated with @Name -- only now, you no longer need the FacesContext to
bridge the gap between Seam and Spring!
Configuring the Seam-Spring integration is surprisingly easy. The first step doesn't even involve any code changes! All you need to do is ensure that you are using both Spring 2.0 and Seam 1.2.0, or a subsequent release. (Seam has gone through rapid changes since the first article in this series was published, so you should be about ready for an upgrade anyway.) The IOC integration is packaged as a separate JAR, jboss-seam-ioc.jar, so you need to include it in your application's classpath in addition to the JARs already mentioned.
The second step doesn't involve the Seam configuration either. This time,
you look at your Spring configuration. First, add the Seam XML schema
declarations to the Spring configuration XML in which your beans are defined.
The standard name for this file is applicationContext.xml, though the example
application uses a more fitting name, spring-beans.xml. Next, add the seam:component tag inside of the definition of any Spring
bean you would like to expose to JSF. In the Open 18 application, you nest
this tag inside of the courseManager bean definition. To see the full
bean listing, refer to the spring-beans.xml
file for this article's example application.
With the improved Spring integration in place, you no longer need to
specify a value binding in the @In annotation atop
the courseManager property, nor do you need the required=false attribute. In its place, use the create=true attribute so that Seam knows to go fetch the
bean from the Spring container and decorate it as a Seam component. The code
in Listing 13 shows the relevant portions of the CourseAction class that are needed to support updating
the geo-spatial point of the course:
Listing 13. The new setPoint method on the CourseAction
@WebRemote
public void setCoursePoint(Long id, Point point) {
System.out.println("Saving new point: " + point + " for course id: " + id);
Course course = courseManager.get(id);
course.setPoint(point);
courseManager.save(course);
}
|
Now put on your shades and act like Joe Cool. That's right: You have officially "Ajaxified" your application!
In this article, you saw the Open 18 application expand from a fairly simple CRUD implementation to a far more sophisticated mashup integrating Ajax-style page rendering and Google Maps. You've also seen what you can do with the Seam Remoting API and Ajax4jsf and learned a more sophisticated way to integrate Seam with the Spring framework.
Both Seam Remoting and Ajax4jsf complement JSF's component model by
extending its abstraction to include Ajax communication. Nowhere in any of the
code presented in this article is it necessary to interact directly with the
XMLHttpRequest JavaScript object. Instead, all the
work is hidden behind higher level APIs that let you either query and
manipulate server-side components directly, or invoke actions and update
regions of a given page. And, thanks to the magic of Ajax, all that work
happens asynchronously.
Over the course of the three articles in this series, you've learned a tremendous amount about JSF development with Seam, but I've by no means exhausted all of Seam's features! Let the discussion and examples in Seamless JSF serve as a starting point for learning even more about what Seam can do to make your JSF projects a pleasure to create. The more you work with Seam, the more you'll find to love about it.
| Description | Name | Size | Download method |
|---|---|---|---|
| Open 18 sample application - Phase 21 | j-seam3.zip | 277K | HTTP |
Information about download methods
Note
- The sample application is organized as a Maven 2 project. All dependencies will be fetched on demand when the build is executed. The application uses several libraries from the Appfuse project to implement the service and DAO layers.
Learn
- "Build Apache Geronimo applications using JavaServer Faces, Part 3: Add Ajax functionality with Ajax4jsf" (Dale de los Reyes, developerWorks, October 2006): Introduce Ajax to an existing JSF application without writing any JavaScript code.
- "Ajax resource center: Articles, tutorials, discussions, downloads, and more from the developerWorks community.
-
Ten Good Reasons To Use Seam: JBoss's listing includes everything from rich client development to "You'll love it if you try it!"
- "Integrating the Google Web Toolkit and JSF using G4jsf" (Sergey Smirnov, TheServerSide.com, August 2006): G4jsf is another exciting technology brought to you by the developers of Ajax4jsf.
- "AJAX without the J: Thoughts on AJAX from a JSF Perspective" (Sergey Smirnov, Exadel Blogs, February 2006): More about Ajax programming without the JavaScript.
-
developerWorks Java technology zone: Hundreds of articles about every aspect of Java programming.
Get products and technologies
-
JBoss Seam: Download it to get the complete distribution, including the bundled example applications.
-
Ajax4jsf: Adds Ajax functionality to existing JSF components.
-
RichFaces: Builds on Ajax4jsf to add out-of-the-box "richness" for JSF Web applications.
-
Firebug: This plug-in for Firefox is an indispensable tool when working with Ajax applications.
-
Maven 2: A software project management and comprehension tool used in the source code samples. Maven automatically downloads dependencies during the build process.
-
Facelets: The preferred JSF view handler for Seam applications.
-
Appfuse: Practically hands you a remarkably comprehensive Maven 2-based project skeleton on which to build your application.
Discuss
-
The Seam blog: Learn what other users think about Seam and give your own feedback in the Seam community forum.
-
developerWorks Ajax forum: An active community for Web developers just learning or actively using Ajax.
-
Check out developerWorks
blogs and get involved in the developerWorks community.

Dan Allen is currently a senior Java engineer at CodeRyte, Inc. He is also a passionate open source advocate and gets a bit manic whenever he catches sight of a penguin. After graduating from Cornell University with a degree in Materials Science and Engineering, Dan became captivated by the world of Linux and open source software. He has been slaving over Web applications ever since, with the last several years focused exclusively on Java-related technologies, including Spring, Hibernate, Maven 2, and the abundant JSF stack. You can keep up with Dan's development experiences by subscribing to his blog at http://www.mojavelinux.com.
Comments (Undergoing maintenance)





