CHAPTER 8

Ajax

Ajax is a technology that has taken the Web by storm and has prompted the Web 2.0 revolution. The technology was originally developed by Microsoft to power a web-based version of its Outlook e-mail software. Microsoft implemented Ajax as an ActiveX control that could be used by its browser, Internet Explorer, and be called from JavaScript to perform asynchronous browser requests.

The advantage of the approach is that the browser doesn't have to refresh the entire page to interact with the server, thus allowing the development of applications that bear a closer resemblance to their desktop counterparts. Since then, browsers other than Internet Explorer have standardized on a native JavaScript object called XMLHttpRequest that has largely the same API as Microsoft's ActiveX control.

The Basics of Ajax

The implications of having different browsers is that you have to write specialized code that detects which browser you are operating in and that loads the XMLHttpRequest object, either as an ActiveX control or as a native object.


Note Microsoft introduced a native JavaScript XMLHttpRequest object in Internet Explorer 7.0, but since Internet Explorer 6.0 is still pretty popular, we recommend you use browser-specific code to obtain the XMLHttpRequest object.


You can see a typical example of obtaining a reference to the XMLHttpRequest object in a cross-browser manner in Listing 8-1.

Listing 8-1. Example of XMLHttpRequest in JavaScript

var req = null;
if (window.XMLHttpRequest) {
      req = new XMLHttpRequest();
} else if (window.ActiveXObject) {
      req = new ActiveXObject("Microsoft.XMLHTTP");
}
if(req!=null) {
   // register an event handler
   req.onreadystatechange = processRequest ;
   // open connection
   req.open("GET",
            "http://localhost:8080/a/remote/location",
            true);
   req.send(); // send request
}
function processRequest(obj) {
        alert(obj.responseXML) // Get the result from the response object
}

The previous code sends an asynchronous request to the http://localhost:8080/a/remote/location address and then, using the onreadystatechange callback event, invokes the processRequest function. This function simply displays an alert box with the content of the response. To illustrate the previous code and help you better understand the flow of an Ajax request, take a look at the UML sequence diagram in Figure 8-1. Remember that Ajax calls are asynchronous.

image

Figure 8-1. An example Ajax flow

As Figure 8-1 illustrates, the browser calls some JavaScript, which in turn creates the XMLHttpRequest object that is able to make the remote call. When the remote call has been made, the XMLHttpRequest object then invokes the callback (in this case the processRequest function), which in turn displays the alert.

Writing JavaScript code as shown in Listing 8-1 can become rather repetitive and tedious. Fortunately, there are Ajax frameworks that encapsulate much of this logic, ranging from the simple (such as Prototype) to the comprehensive (such as Dojo). Efforts are underway to standardize on a JavaScript library, but as is always the case with any collaborative effort, this could be a long and painful process that will likely never satisfy everyone.

Knowing this, the developers of Grails have designed an "adaptive" Ajax implementation that allows you to decide which Ajax library is most suitable for your needs. By default, Grails ships with the Prototype library and counterpart Scriptaculous effects library; however, through Grails' plugin system, you can add support for alternative libraries that supply the underlying implementation of Grails' Ajax tags.

Before you delve into the world of Ajax tags, you need to revisit the gTunes application, since you'll be enhancing the gTunes application by adding a range of Ajax-powered features that improve the user experience:

  • The ability to log in asynchronously
  • A new feature that allows you to search and filter songs within your library and the store using Ajax-powered search fields
  • And finally, a slicker way of displaying albums and songs including album art

So, before getting too carried away, let's move on to the guts of the chapter by improving the gTunes application interface, Ajax style.

Ajax in Action

To begin with, let's start with a simple example. At a basic level, Grails provides a set of tags that simplify the creation of Ajax-capable components such as links, forms, and text fields. For example, to create an HTML anchor tag that when clicked executes an Ajax call, you can use the <g:remoteLink> tag. Let's try a "Hello World"–style example using <g:remoteLink>. First update StoreController by adding the action shown in Listing 8-2.

Listing 8-2. An Action That Renders the Date and Time

def showTime = {
    render "The time is ${new Date()}"
}

The showTime action in Listing 8-2 uses the render method introduced in Chapter 4 to render a plain-text response to the client that contains the current date and time, trivially obtained through Java's java.util.Date class. That was simple enough; now open the index.gsp file located in the grails-app/views/store directory. Before you attempt to use the <g:remoteLink> tag, you need to tell Grails which Ajax library to use. You can do this through the <g:javascript> tag, which needs to go in the <head> section of your index.gsp file, as shown in Listing 8-3.

Listing 8-3. Using the Prototype Library

<g:javascript library="prototype" />

In this case, you are telling Grails to use the Prototype library for Ajax. As a side effect, Grails will import all the necessary Prototype dependencies into the page, so you're ready to go. Now, within the body of the index.gsp page, add the code shown in Listing 8-4, which uses the <g:remoteLink> tag.

Listing 8-4. Using the Tag

<g:remoteLink action="showTime" update="time">Show the time!</g:remoteLink>
<div id="time">
</div>

What this does is add an HTML anchor tag (with the text "Show the time!") to the page, which when clicked will execute an asynchronous request to the showTime action of the StoreController. The update attribute of the <g:remoteLink> tag specifies the ID of the DOM element into which you would like to place the contents of the response. In this case, you've provided an HTML <div> element with an ID of time just below the <g:remoteLink> that will be the target of this Ajax call.

And with that, you have completed a trivial example of Ajax-enabling your application. Try clicking the link to see what happens. You will note that the current date and time gets placed into the <div> each time you click the link! Figure 8-2 shows an example of this behavior.

image

Figure 8-2. A Simple Ajax call example

Changing Your Ajax Provider

So, as it stands, you are using Prototype as the underlying library that powers the <g:remoteLink> tag, but what if you wanted to use a different library? Grails lets you swap to a different implementation via its plugin system. For example, say you wanted to use the Yahoo UI plugin instead; then simply run this:

$ grails install-plugin yui

Now modify the <g:javascript> tag from Listing 8-3, changing the value of the library attribute to yui:

<g:javascript library="yui" />

Now refresh the page and try the "Show the time!" link again. Like magic, Grails is now using Yahoo UI instead of Prototype. In addition to Yahoo UI, there are plugins for Dojo, Ext-JS, and jQuery. The Grails plugins page at http://grails.org/Plugins provides the latest up-to-date information on the available plugins.

Asynchronous Form Submission

Now that you have had a chance to explore a trivial example, let's try something a little more challenging. When building Ajax applications, it is often useful to submit a form and its data to the server asynchronously. Currently, the login process of the gTunes application uses a regular form submission, but wouldn't it be useful to allow users to log in without a refresh?

Right now, the login form contained within the grails-app/views/layouts/main.gsp layout submits using a regular form. In other words, the form submission is synchronous and doesn't occur in a background process as an Ajax request would. Luckily, Grails provides the <g:formRemote> tag—an enhanced version of the HTML form tag that enables the form to submit as an Ajax request.

However, before you migrate the regular <g:form> tag to its infinitely more interesting cousin <g:formRemote>, let's move the code that renders the login form into its own GSP template. The importance of doing this will become clear later. For now, create a new file called grails-app/views/user/_loginForm.gsp, which will form the basis for the template, and then cut-and-paste the code from the layout so that the template looks like Listing 8-5.

Listing 8-5. The Login Template

<g:form
       name="loginForm"
       url="[controller:'user',action:'login']">
     ...
</g:form>
<g:renderErrors bean="${loginCmd}"></g:renderErrors>

Now within the main.gsp layout, use the <g:render> tag to render the template, as shown in Listing 8-6.

Listing 8-6. Using the Tag to Display the Login Form

<div id="loginBox">
    <g:render template="/user/loginForm"></g:render>
</div>

With that done, it is time to introduce the usage of <g:formRemote>. First simply rename the <g:form> tag references to <g:formRemote>, and then add the update attribute (mentioned in the previous section about the <g:remoteLink> tag) to the <g:formRemote> tag. In this case, the update attribute refers to the DOM ID of the loginBox <div>. And that is it; the changes to the code appear in Listing 8-7 in bold.

Listing 8-7. Altering the Login Form to Use <g:formRemote>

<g:formRemote
             name="loginForm"
             url="[controller:'user',action:'login']"
             update="loginBox">
             ...
</g:formRemote>

The remainder of the code stays the same. The <g:formRemote> tag is still submitting to the login action of the UserController, and no change is required to any of the input fields or the submit button. Now if you refresh the page and try to log in, a surprising thing will happen. Astoundingly, you get the contents of the entire page placed within the loginBox <div>! This happens because you updated the client code but paid no attention to the server logic, which is still displaying the entire view. To correct this problem, you need to revisit the server-side code to render only a snippet of HTML instead of the entire page.

Just in case you don't recall the code in question, Listing 8-8 shows what the current code for the login action of the UserController looks like.

Listing 8-8. The Current login Action Code

def login = { LoginCommand cmd ->
    if(request.method == 'POST') {
        if(!cmd.hasErrors()) {
            session.user = cmd.getUser()
            redirect(controller:'store')
        }
        else {
            render(view:'/store/index', model:[loginCmd:cmd])
        }
    }
    else {
        render(view:'/store/index')
    }
}

At the moment, the code in Listing 8-8 renders the entire grails-app/views/store/index.gsp view, but what you actually want is for only the login form to be displayed again (on login failure) or a welcome message to be displayed if the user successfully logged in. Let's refactor the code to achieve this goal; Listing 8-9 shows the result.

Listing 8-9. Handing an Ajax Login Request

def login = { LoginCommand cmd ->
    if(request.method == 'POST') {
        if(!cmd.hasErrors()) {
            session.user = cmd.getUser()
            render(template:"welcomeMessage")
        }
        else {
            render(template:'loginForm', model:[loginCmd:cmd])
        }
    }
    else {
        render(template:'loginForm')
    }
}

You could, of course, take this further and deal with both Ajax and regular requests, but for the moment that isn't a requirement. As you can see from the code in Listing 8-9, what you're doing is using the template argument of the render method instead of the view argument, which allows you to reuse the _loginForm.gsp template. In addition, you'll need to create a grails-app/views/user/_welcomeMessage.gsp template to deal with a successful login, the contents of which you can see in Listing 8-10.

Listing 8-10. The _welcomeMessage.gsp Template

<div style="margin-top:20px">
    <div style="float:right;">
        <g:link controller="user"
                    action="profile"
                    id="${session.user.id}">Profile</g:link> |
        <g:link controller="user" action="logout">Logout</g:link><br>
    </div>

    Welcome back <span id="userFirstName">${session?.user?.firstName}!</span>
    <br><br>

    You have purchased (${session.user.purchasedSongs?.size() ?: 0}) songs.<br>
</div>

Executing Code Before and After a Call

Each of the Ajax tags supports two attributes called before and after, which allow the insertion of arbitrary JavaScript code to be executed before and after a remote call.


Note The code within the after attribute will be executed whether or not the remote call is successful. In this sense, it should not be compared to an onComplete event handler.


For example, you could use a before call to programmatically alter the value of the field before it is sent to the server, as shown in Listing 8-11.

Listing 8-11. Example Before Attribute Usage

<g:javascript>
    function setDefaultValue(form) {
        if(form.elements[0].value == '') {
            form.elements[0].value = 'changed'
        }
    }
</g:javascript>
<g:formRemote action="login"
              before="setDefaultValue(this);"
              update="loginBox"
              name="loginForm">
...
</g:formRemote>

Here, you set a default value for the first element of the <form> (maybe a hidden or optional field) before it is sent to the server. It is important to understand that the before and after attributes are not event hooks. This becomes more apparent when using after, which will execute directly after an Ajax call and will not wait until it returns. In other words, it has no awareness of whether the Ajax call is successful. Events are a different concept that will be covered in detail in the next section.

Handling Events

An important aspect of Ajax development, and indeed any asynchronous development style, is the ability to receive and act on events. To this end, Grails' Ajax tags allow the registration of a number of different event handlers that you can take advantage of. For example, a common use case in Ajax development is to provide some form of feedback to the user while an Ajax call is happening, be it an activity monitor, a progress bar, or a simple animated icon (such as a spinner or an hourglass).

To accomplish this for a single Ajax call, you could use the onLoading and onComplete events, as shown in Listing 8-12.

Listing 8-12. Displaying a Progress Indicator

<g:formRemote
       url="[controller:'user',action:'login']"
       onLoading="showProgress();"
       onComplete="hideProgress();"
       update="loginBox"
       name="loginForm">
    ...
</g:formRemote>

Note If you are using the Prototype library, you can take advantage of Prototype's generalized responders mechanism, which allows you to centralize Ajax event logic to provide generic behavior across all Ajax calls (such as displaying a progress indicator). Refer to http://www.prototypejs.org/api/ajax/responders for more information.


Listing 8-12 uses two hypothetical JavaScript methods called showProgress() and hideProgress() to display feedback to the user. These could be as simple as displaying an animated graphic or something more advanced such as polling the server for the current state of a large operation and displaying a progress bar.

Table 8-1 shows the different events. The last event in the table deserves special mention, because it allows you to handle specific error codes. This is often useful to display alert boxes or specific feedback to the user, such as certain codes when the server is down or being maintained. In the next section, we'll cover more advanced ways to perform updates on content.

Table 8-1. Table of Ajax Events

Event Name Description
onSuccess Called when the remote call is successful
onFailure Called when the remote call begins to load the response
onLoaded Called when the remote call has loaded the response, but prior to any
onComplete Called when the response has been received and any updates are completed
onERROR_CODE Called for specific error codes such as on404

Fun with Ajax Remote Linking

In your first introduction to the <g:remoteLink> tag, you implemented a simple bit of functionality that displayed the time when the anchor tag was clicked. It's not exactly groundbreaking stuff, we know. Let's correct this by looking at a more advanced example.

In Chapter 5, you created a few panels for the right side of the gTunes store that displayed the newest additions to the gTunes library for songs, albums, and artists, respectfully. As a refresher, Listing 8-13 shows the code in question from the grails-app/views/store/shop.gsp file.

Listing 8-13. The Latest Content Panel

<div id="top5Panel" class="top5Panel">
    <h2>Latest Albums</h2>
    <div id="albums" class="top5Item">
        <g:render template="/album/albumList" model="[albums: top5Albums]" />
    </div>
    <h2>Latest Songs</h2>
    <div id="songs" class="top5Item">
        <g:render template="/song/songList" model="[songs: top5Songs]" />
    </div>
    <h2>Newest Artists</h2>
    <div id="artists" class="top5Item">
        <g:render template="/artist/artistList" model="[artists: top5Artists]" />
    </div>
</div>

Each of these uses a specific template to render a simple HTML unordered list for each category. It would be nice if the list items, instead of being plain text, consisted of HTML links that used Ajax to display details about the Album, Song, or Artist in question.

Let's start with Album. If you recall from the domain model, an Album has a title, release year, genre, artist, and a list of Songs that apply to that album. To begin with, create a template that can render that information. Listing 8-14 shows the grails-app/views/album/_album.gsp template.

Listing 8-14. Implementing the _album.gsp Template

<div id="album${album.id}" class="album">
    <div class="albumDetails">
        <div class="artistName">${artist.name}</div>
        <div class="albumTitle">${album.title}</div>
        <div class="albumInfo">
            Genre: ${album.genre ?: 'Other'}<br>
            Year: ${album.year}
        </div>
        <div class="albumTracks">
            <ol>
                <g:each in="${album.songs?}" var="song">
h                    <li>${song.title}</li>
                </g:each>
            </ol>
        </div>
       <div class="albumLinks">
    </div>
</div>

Now that you have a template, you can alter the grails-app/views/album/_albumList.gsp template to use <g:remoteLink> to call a controller action called display on the AlbumController for each item in the list. Listing 8-15 shows (in bold) the changes made to the _albumList.gsp template.

Listing 8-15. Updating _albumList.gsp to Use

<ul>
    <g:each in="${albums?}" var="album">
        <li><g:remoteLink update="musicPanel"
                          controller="album"
                          action="display"
                          id="${album.id}">${album.title}</g:remoteLink></li>
    </g:each>
</ul>

Notice how you can use the update attribute to specify that you want the contents of the response to be placed into an HTML <div> that has a DOM ID with the value musicPanel. If you refresh the page at this point and try the links, you'll notice that the Ajax part of the picture is working already! The downside is that since there is no display action in the AlbumController at this point, you get a 404 "Page not found" error from the server.

Let's correct that by opening AlbumController and implementing the display action. Listing 8-16 shows the code, which simply obtains the Album instance using the id parameter from the params object and then uses it to render the _album.gsp template developed in Listing 8-14.

Listing 8-16. The display Action of AlbumController

def display = {
    def album = Album.get(params.id)
    if(album) {
        def artist = album.artist
        render(template:"album", model:[artist:artist, album:album])
    }
    else {
        render "Album not found."
    }
}

By adding a bit of CSS magic to enhance the look of the _album.gsp template, all of a sudden you have album details being obtained via Ajax and rendered to the view. Figure 8-3 shows the result of your hard work.

image

Figure 8-3. Displaying albums using Ajax

Sadly, even with the CSS enhancements, Album details are looking a bit bland with all that text. Wouldn't it be nice to be able to display the album art for each album? Where there is a will, there is a way, and luckily, Amazon has come to the rescue here by providing a web services API that lets developers look up album art from its massive pool of assets.

Even better, it has a Java API, which encapsulates the communication with the web service, perfect for our needs. To complete the initial setup phase, follow these simple steps:

  1. Sign up for a free Amazon web services account at https://aws-portal.amazon.com/gp/aws/developer/account/index.html, and obtain your Amazon access key (you'll be needing it).
  2. Then download the "Java Library for Amazon Associates Web Service" file from the following location: http://developer.amazonwebservices.com/connect/entry.jspa?externalID=880&ref=featured.
  3. Extract the .zip file, and copy the amazon-a3s-*-java-library.jar file into your project lib directory.
  4. Copy the required dependencies commons-codec-1-3.jar and commons-httpclient-3.0.1.jar from the third-party/jakarta-commons directory to your project's lib directory.
  5. Copy all the JARs contained with the third-party/jaxb directory to your project's lib directory.

After going through these steps, you should have set up your project's lib directory in a similar fashion to Figure 8-4.

image

Figure 8-4. Setting up the appropriate JARs for Amazon Web Services

With that done, it is time to create your first service. The capabilities of services will be described in more detail in Chapter 11, but as a simple definition, services are useful for centralizing business logic that needs to be shared across layers (such as from a tag library and a controller). You're going to create an AlbumArtService that deals with obtaining album art from Amazon. To do this, start by running the grails create-service command:

$ grails create-service com.g2one.gtunes.AlbumArt

The create-service command will create a new empty AlbumArtService that resembles Listing 8-17.

Listing 8-17. The AlbumArtService Template

package com.g2one.gtunes

class AlbumArtService {
}

One thing to note about services is that they are by default transactional. In other words, each public method is wrapped in a Spring-managed transaction, making all persistence operations atomic. The implications of this are covered in more detail in Chapter 11; for the moment, since this service is not performing any persistence operations, you can disable this behavior by setting the transactional static property to false:

static transactional = false

With that out of the way, your first job is to provide the AlbumArtService with the Amazon access key you obtained earlier. To achieve this, add a String property called accessKeyId to the AlbumArtService, such as the one shown here:

String accessKeyId

Now you can use a technique called property override configuration to specify the value of this property in grails-app/conf/Config.groovy. Every service in Grails translates into a singleton Spring bean. The name of the bean is formulated from the class name using bean conventions. Hence, the bean name for AlbumArtService will be albumArtService. You can set properties on the albumArtService bean from Config.groovy by using the beans block, as shown in Listing 8-18.

Listing 8-18. Configuring Beans Using Config.groovy

beans {
    albumArtService {
        // Set to your Amazon Web Services Access key to enable album art
        accessKeyId = "8DSFLJL34320980DFJ" // Not a real Amazon access key!
    }
}

The advantage of this approach is that thanks to the features offered by Config.groovy, you can easily specify per-environment access keys rather than hard-coding the key into the AlbumArtService class. So, with the accessKeyId set, it's time to step through the implementation of the AlbumArtService. The first thing you need to do is provide a method called getAlbumArt that takes the Artist name and Album title:

String getAlbumArt(String artist, String album) {
    ...
}

Now you need to create an instance of the Amazon ItemSearchRequest class (remember to import the package!) and populate it with the Artist name Album title info, the index you want to search, and the response group you're after:

import com.amazonaws.a2s.model.*
..
def request = new ItemSearchRequest()
request.searchIndex = 'Music'
request.responseGroup = ['Images']
request.artist = artist
request.title = album

After creating an ItemSearchRequest instance, you need to pass it to the AmazonA2SClient class to obtain a response:

import com.amazonaws.a2s.*
..
def client = new AmazonA2SClient(accessKeyId, "")
def response = client.itemSearch(request)

With the response in hand, you can extract the information you want by indexing into the response:

return response.items[0].item[0].largeImage.URL

Great! You have something that works, but it is heavily optimized for the happy path, so what happens when things go wrong? Or if the accessKeyId is misconfigured? Or, heaven forbid, if you pass in a null artist or album? This is where unit testing best practices come in handy.

So, before you get too far ahead of yourself, let's create some tests to verify this thinking. Create an AlbumArtServiceTests test suite within the test/unit directory and in the same package as the AlbumArtService by running this command:

grails create-unit-test com.g2one.gtunes.AlbumArtServiceTests

Now let's test what happens if there is no accessKeyId:

void testNoAccessKey() {
    def albumArtService = new AlbumArtService()
    assertNull albumArtService.getAlbumArt("foo", "bar")
}

Run the test by executing the test-app command and passing in the name of the test suite. For example:

grails test-app com.g2one.gtunes.AlbumArtService

The result? An error. Unsurprisingly, the AmazonA2SClient was unhappy that you failed to specify a valid accessKeyId and threw an exception. You need to deal with the case where there is no accessKeyId specified.

The logical thing to do in this case is to return some default image to be rendered since one cannot be obtained from Amazon without an accessKeyId. Let's specify a constant that holds the location of this default image:

static final DEFAULT_ALBUM_ART_IMAGE = "/images/no-album-art.gif"

Now wrap the code in an if/else block to ensure that if no accessKeyId is available you return the default value:

String getAlbumArt(String artist, String album) {
    if(accessKeyId)
        ...
    }
    else {
        log.warn """No Amazon access key specified.
        Set [beans.albumArtService.accessKeyId] in Config.groovy"""
        return DEFAULT_ALBUM_ART_IMAGE
    }
}

Good work, but hang on...the test is still failing? Since the previous assertion was checking for a null return value and not the default image location, you need to change the test:

void testNoAccessKey() {
    def albumArtService = new AlbumArtService()
    assertEquals AlbumArtService.DEFAULT_ALBUM_ART_IMAGE,
                             albumArtService.getAlbumArt("foo", "bar")
}

Now let's test what happens if an exception emerges from the AmazonA2SClient for any other reason—maybe a network outage or corrupt data. Since this is a unit test, you don't want to actually communicate with Amazon in the test because that would slow the test down. You can use metaprogramming techniques to provide a mock implementation of the AmazonA2SClient's itemSearch method instead via ExpandoMetaClass, as in Listing 8-19.

Listing 8-19. Mocking Methods with ExpandoMetaClass

void testExceptionFromAmazon() {
   AmazonA2SClient.metaClass.itemSearch = { ItemSearchRequest request ->

            throw new Exception("test exception")
    }
     def albumArtService = new AlbumArtService()
     albumArtService.accessKeyId = "293473894732974"

     assertEquals AlbumArtService.DEFAULT_ALBUM_ART_IMAGE,
                              albumArtService.getAlbumArt("Radiohead", "The Bends")
}
void tearDown() {
    GroovySystem.metaClassRegistry.removeMetaClass(AmazonA2SClient)
}

The key line of Listing 8-19 is highlighted in bold as you override the default implementation of the itemSearch method to simply throw an exception. (You can find out more about metaprogramming techniques in Appendix A.) If you run this test now, it will fail with an error. Why? The reason is simple—you are not currently catching any exceptions. This is one area where writing good tests really helps identify potential weaknesses in your code.

To correct the problem, update AlbumArtService to wrap the call to the Amazon client in a try/catch block, as in Listing 8-20.

Listing 8-20. Gracefully Dealing with Exceptions from Amazon

String getAlbumArt(String artist, String album) {
    ...
    try {
        ...
    }
    catch(Exception e) {
        log.error "Problem communicating with Amazon: ${e.message}", e
        return DEFAULT_ALBUM_ART_IMAGE
    }
    ...
}

Phew. You're nearly done; there is just one more thing to consider. Whenever dealing with any remote resource, you have to consider the performance implications. Currently, you're asking Amazon to look up album art each time you call the getAlbumArt method. However, since there is a high likelihood that you'll be calling the getAlbumArt method repeatedly with the same data, it makes sense to cache the result from Amazon.

To do this, you could just store the results in a local map, but what if the site grows really big? Its memory consumption could become problematic. Really, you need a more mature caching solution where you can configure the eviction policy. You can set up an Ehcache instance to hold the cached data. Ehcache is a mature open source caching library that ships with Grails. To set up Ehcache, open the grails-app/conf/spring/resources.groovy file. This script allows you to configure additional Spring beans that can be injected into any Grails-managed artifact such as a controller, service, or tag library.

We'll be going into a great more detail about Spring and Spring beans in Chapter 16, but for now it's enough to know that each method call within the beans closure translates into a Spring bean. The name of the method is the bean name, while the first argument to the method is the bean class. Properties of the bean can be set in the body of the closure that is specified as the last argument.


Note A Spring bean is, typically, a singleton instance of a Java class that is managed by Spring, which by implication makes it injectable into any Grails instance such as a controller or tag library.


To translate this into practice, Listing 8-21 shows how to use Spring's EhCacheFactoryBean class to set up an Ehcache instance that expires every 5 minutes (or 300 seconds).

Listing 8-21. Configuring an Ehcache Spring Bean

beans = {
    albumArtCache(org.springframework.cache.ehcache.EhCacheFactoryBean) {
        timeToLive = 300
    }
}

With that done, you need to augment the existing AlbumArtService to leverage the albumArtCache bean. The first thing you need to consider is the cache key. In other words, what logical variable or set of variables is required to look up the data you are interested in? In this case, the key consists of the artist and album arguments passed to the getAlbumArt method.

To formulate a logical key that models these two arguments, you need to create a new Serializable class called AlbumArtKey and implement equals and hashCode according to the rules defined in the javadocs for these methods. Listing 8-22 shows a possible implementation that assumes that the artist and album are required.

Listing 8-22. The AlbumArtKey Cache Key Class

class AlbumArtKey implements Serializable {
    String artist
    String album
    boolean equals(other) {    artist.equals(other.artist) &&
                                              album.equals(other.album) }
    int hashCode() { artist.hashCode() + album.hashCode()}
}

With the cache key done, it's time to put it to use in the AlbumArtService. Listing 8-23 shows the albumArtCache in action; notice how you can use Groovy's safe-dereference operator so that the code works even if the albumArtCache is not provided.

Listing 8-23. Enabling Caching in AlbumArtService

import net.sf.ehcache.Element
...
def albumArtCache
...
String getAlbumArt(String artist, String album) {
    ...
    def key = new AlbumArtKey(album:album, artist:artist)
    def url = albumArtCache?.get(key)?.value
    if(!url) {
       // amazon look-up here
       ...
       url = response.items[0].item[0].largeImage.URL
       albumArtCache?.put(new Element(key, url))
    }
    return url
}

Excellent—you have now completed the AlbumArtService, and it is ready to be injected into a controller or tag library near you. For reference, Listing 8-24 shows the full code for the AlbumArtService, summarizing all that you have achieved.

Listing 8-24. The AlbumArtService

package com.g2one.gtunes

import org.codehaus.groovy.grails.commons.*
import com.amazonaws.a2s.*
import com.amazonaws.a2s.model.*
import net.sf.ehcache.Element

class AlbumArtService {
    static transactional = false
    static final DEFAULT_ALBUM_ART_IMAGE = "/images/no-album-art.gif"

    String accessKeyId
    def albumArtCache

    String getAlbumArt(String artist, String album) {

        if(accessKeyId) {
            if(album && artist) {
                def key = new AlbumArtKey(album:album, artist:artist)
                def url = albumArtCache?.get(key)?.value
                if(!url) {
                    try {
                        def request = new ItemSearchRequest()
                        request.searchIndex = 'Music'
                        request.responseGroup = ['Images']
                        request.artist = artist
                        request.title = album

                        def client = new AmazonA2SClient(accessKeyId, "")

                         def response = client.itemSearch(request)

                         // get the URL to the amazon image (if one was returned).
                         url = response.items[0].item[0].largeImage.URL
                         albumArtCache?.put(new Element(key, url))
                     }
                     catch(Exception e) {
                         log.error "Problem communicating with Amazon: ${e.message}",
                                          e
                         return DEFAULT_ALBUM_ART_IMAGE
                      }
                  }
                  return url
              }
              else {
                  log.warn "Album title and Artist name must be specified"
                  return DEFAULT_ALBUM_ART_IMAGE
              }
          }
          else {
              log.warn """No Amazon access key specified.
              Set [beans.albumArtService.accessKeyId] in Config.groovy"""
              return DEFAULT_ALBUM_ART_IMAGE
          }

      }
}
class AlbumArtKey implements Serializable {
    String artist
    String album
    boolean equals(other) {    artist.equals(other.artist) &&
                                              album.equals(other.album) }
    int hashCode() { artist.hashCode() + album.hashCode() }
}

Now, given that you'll want to display album art in the view, it makes sense to create a custom tag that encapsulates that logic. Enter the AlbumArtTagLib. To create the AlbumArtTagLib, run the following command:

$ grails create-tag-lib com.g2one.gtunes.AlbumArt

This will result in a new tag library being created at the location grails-app/taglib/com/g2one/gtunes/AlbumArtTagLib.groovy. As you discovered in Chapter 5, tag libraries can be placed in a namespace. Namespaces provide logical groupings for tags. Let's define a music namespace for the AlbumArtTagLib; see Listing 8-25.

Listing 8-25. Defining the music Namespace

package com.g2one.gtunes

class AlbumArtTagLib {
    static namespace = "music"
    ...
}

To inject the AlbumArtService into the AlbumArtTagLib, simply define a property that matches the bean naming conventions for the AlbumArtService:

def albumArtService

Now you need to create a tag within the AlbumArtTagLib that is capable of outputting an HTML <img> tag with the necessary album art URL populated. The <music:albumArt> tag will take three attributes: an artist, an album, and an optional width attribute. The remaining attributes should be added to the attributes of the HTML <img> tag that is output. Listing 8-26 shows the implementation of the <music:albumArt> tag with usage of the albumArtService highlighted in bold.

Listing 8-26. The <music:albumArt> Tag

def albumArt = { attrs, body ->
    def artist = attrs.remove('artist')?.toString()
    def album = attrs.remove('album')?.toString()
    def width = attrs.remove('width') ?: 200
    if(artist && album) {
        def albumArt = albumArtService.getAlbumArt(artist, album)
        if(albumArt.startsWith("/"))
                 albumArt = "${request.contextPath}${albumArt}"
        out << "<img width="$width" src="${albumArt}" border="0" "
        attrs.each { k,v-> out << "$k="${v?.encodeAsHTML()}" "}
        out << "></img>"
    }
}

You can test the <music:albumArt> tag using Grails' excellent GroovyPagesTestCase, which allows you to test GSP tags directly. The grails create-tag-lib command already created an integration test at the location test/integration/com/g2one/gtunes/AlbumArtTagLibTests, which serves as a starting point for the test. The functionality being tested is similar to the AlbumArtServiceTests suite you developed earlier, so (for the sake of brevity) we won't go through every test. However, Listing 8-27 shows how simple extending GroovyPagesTestCase makes testing the <music:albumArt> tag, by calling the assertOutputEquals method that accepts the expected output and the template to use for rendering.

Listing 8-27. Testing the <music:albumArt> Tag with GroovyPagesTestCase

package com.g2one.gtunes

import grails.test.*
...
class AlbumArtTagLibTests extend GroovyPagesTestCase {
    ...
    void testGoodResultFromAmazon() {
        AmazonA2SClient.metaClass.itemSearch = { ItemSearchRequest request ->
            [items:[[item:[[largeImage:[URL:"/mock/url/album.jpg"]]]]]] }

        albumArtService.accessKeyId = "293473894732974"

       def template = '<music:albumArt artist="Radiohead" album="The Bends" />'
       def expected = '<img width="200" src="/mock/url/album.jpg" border="0"></img>'
       assertOutputEquals expected, template
    }
}

Finally, to put all the pieces together, you need to change the grails-app/views/album/_album.gsp template so that it can leverage the newly created <music:albumArt> tag. Listing 8-28 shows the amendments to _album.gsp in bold.

Listing 8-28. Adding Album Art to the _album.gsp Template

<div id="album${album.id}" class="album">
    <div class="albumArt">
        <music:albumArt artist="${artist}" album="${album}" />
    </div>
    ...
</div>

After further CSS trickery, Figure 8-5 shows what the new album art integration looks like. Much better!

image

Figure 8-5. The _album.gsp template with integration album art

Adding Effects and Animation

What you've achieved so far is pretty neat, but it would be useful to spice it up with a few effects. As well as Prototype, Grails ships with Scriptaculous (http://script.aculo.us/), which is a JavaScript effects and animation library.

To start using Scriptaculous, open the grails-app/views/layouts/main.gsp layout, and change the <g:javascript> tag that currently refers to prototype to this:

<g:javascript library="scriptaculous" />

Now say you want albums to fade in when you click the "Latest Album" links; the first thing to do is to make sure albums are hidden to begin with. To do so, open the grails-app/views/album/_album.gsp template, and ensure the main HTML <div> has its style attribute set to display:none, as in Listing 8-29.

Listing 8-29. Hiding the Album

<div id="album${album.id}" class="album" style="display:none;">
    ...
</div>

Now you could use Ajax events such as onComplete, which were discussed in an earlier section, to execute the effect. However, since that would require ensuring every <g:remoteLink> tag contained the onComplete attribute, it is probably better to use an embedded script inside the template. Try adding the following to the bottom of the _album.gsp template:

<g:javascript>
    Effect.Appear($('album${album.id}'))
</g:javascript>

This executes the Appear effect of the Scriptaculous library. Now whenever you click one of the "Latest Album" links, the album fades in nicely. Scriptaculous has tons of other effects, so it is worth referring to the documentation at http://script.aculo.us/ to find out what is available. Also, most notable Ajax libraries—many of which offer Grails plugins—also feature similar capabilities. Make sure you explore what is available in your Ajax library of choice!

Ajax-Enabled Form Fields

Wow, that previous section was quite an adventure. But you're not done with Ajax yet. In this section, you'll learn how you can enable Ajax on form fields such as text inputs.

This is often useful if you're implementing features such as autocomplete or an instant search capability like Spotlight on Mac OS X. In fact, search is exactly what you're going to aim to achieve in this section. Sure, it is useful to be able to click the latest additions to the music library, but it is critical that users of gTunes can search the back catalog of songs and albums.

Luckily, Grails provides an extremely useful tag to help implement the search feature—the <g:remoteField> tag. As you might guess from the name, <g:remoteField> is a text field that sends its value to the server whenever it changes. This is exactly what you'll need for a Spotlight-like search facility.

As a start, open the grails-app/views/store/shop.gsp view, and add the <g:remoteField> search box, as shown in Listing 8-30.

Listing 8-30. Using the <g:remoteField> Tag

<div id="searchBox">
    Instant Search: <g:remoteField
                               name="searchBox"
                               update="musicPanel"
                               paramName="q"
                               url="[controller:'store', action:'search']" />
</div>

What you have done here is set up a <g:remoteField> that sends a request to the search action of the StoreController. Using the paramName attribute, you can configure the name of the parameter that will contain the value of the input field when it is sent. If you don't specify the paramName attribute, then the <g:remoteField> tag defaults to sending a parameter named value. Figure 8-6 shows what the search box looks like.

Figure 8-6. The gTunes instance search box

If you refresh the page and start typing, you can see that field is already sending remote requests, although you're getting 404 errors in the page rather than anything useful. At this point, it is worth considering how to implement search. You could, of course, use Hibernate queries, but Hibernate is not really designed to be used as a search engine, and designing your own search query language would be a pain.

Needless to say, the Grails plugin system comes to the rescue once again. One of the most popular plugins currently available for Grails is the Searchable plugin, which builds on Compass (http://www.compass-project.org/) and Lucene (http://lucene.apache.org/).


Note The full documentation for Searchable is available at http://grails.org/Searchable+Plugin.


As usual, installing Searchable is trivial. Just run the following command:

$ grails install-plugin searchable

The Searchable plugin integrates with Grails by providing the ability to expose Grails domain classes as searchable entities. At a simple level, it is possible to add search capabilities by adding the following line to the domain class you want to search:

static searchable = true

However, it is typically the case that you want to search only a subset of the properties of the domain class. This is, of course, perfectly possible with Searchable, and in fact it defines an entire DSL for mapping between your classes and the search index (a topic beyond the scope of this book).

In this case, you want to be able to search on an album or song's genre and title and on an artist's name. Listing 8-31 shows how to enable the aforementioned behavior using Searchable.

Listing 8-31. Enabling Search on the gTunes domain

class Song {
    static searchable = [only: ['genre', 'title']]
    ...
}
class Album {
    static searchable = [only: ['genre', 'title']]
    ...
}
class Artist {
    static searchable = [only: ['name']]
    ...
}

That was simple enough. Next, it is time to implement the search action of the StoreController. Like GORM, Searchable provides a bunch of new methods on domain classes that support searching, including the following:

  • search: Returns a search result object containing a subset of objects matching the query
  • searchTop: Returns the first result object matching the query
  • searchEvery: Returns all result objects matching the query
  • countHits: Returns the number of hits for a query
  • termFreqs: Returns term frequencies for the terms in the index (advanced)

For a full reference on what each method does and how it behaves, refer to the documentation at http://grails.org/Searchable+Plugin. For your needs, you're going to use the search method to formulate the search results. Listing 8-32 shows the implementation of the search action of the StoreController using Searchable APIs.

The code is pretty simple. It obtains the q parameter representing the query and, if it isn't blank, builds a model that contains search results for albums, artists, and songs. One interesting aspect of this code is the trySearch method, which demonstrates a compelling use of Groovy closures to deal with exceptions. Since an exception will likely be because of an error in the search syntax, it is preferable to log that error and return an empty result rather than throwing the error back to the user.

Once the search results have been formulated within a searchResults variable, the code renders a _searchResults.gsp template, passing the searchResults as the model. As Listing 8-33 demonstrates, the grails-app/views/store/_searchResults.gsp template is trivial and simply reuses the existing templates such as _albumList.gsp and _artistList.gsp to display results.

Listing 8-33. The _searchResults.gsp Template

<div id="searchResults" class="searchResults">

    <g:if test="${albumResults?.results}">
        <div id="albumResults" class="resultsPane">
            <h2>Album Results</h2>
            <g:render template="/album/albumList"
                               model="[albums:albumResults.results]"></g:render>

        </div>
    </g:if>
    <g:if test="${artistResults?.results}">
        <div id="artistResults" class="resultsPane">
            <h2>Artist Results</h2>
            <g:render template="/artist/artistList"
                                model="[artists:artistResults.results]"></g:render>
        </div>
    </g:if>

    <g:if test="${songResults?.results}">
        <div id="songResults" class="resultsPane">
            <h2>Song Results</h2>
            <g:render template="/song/songList"
                                model="[songs:songResults.results]"></g:render>
        </div>

    </g:if>
</div>

After calling on your CSS prowess once more, you now have nicely formulated search results appearing, and even better, because they're using the same <g:remoteLink> tag as the "Latest Albums" lists on the right of the screen, they're already Ajax-enabled out of the box. Simply by clicking one of the search results, you get an Album's details pulled in via Ajax! Figure 8-7 shows the usage of the search box and demonstrates how wildcard capabilities using the asterisk (*) character are supported thanks to the Searchable plugin.

image

Figure 8-7. Instant search results using <g:remoteField> and Searchable

A Note on Ajax and Performance

It is important to note the impact that using Ajax has on an application's performance. Given the number of small snippets of code that get rendered, it will come as little surprise that badly designed Ajax applications have to deal with a significantly larger number of requests. What you have seen so far in this chapter is a naïve approach to Ajax development. You have waved the Ajax magic wand over your application with little consideration of the performance implications.

Nevertheless, it is not too late to take some of these things into account. You can use several techniques to reduce the number of requests an Ajax application performs before you start throwing more hardware at the problem.

The first thing to remember is that an Ajax call is a remote network call and therefore expensive. If you have developed with EJB, you will recall some of the patterns used to optimize EJB remote method calls. Things such as the Data Transfer Object (DTO) are equally applicable in the Ajax world.

Fundamentally, the DTO pattern serves as a mechanism for batching operations into a single call and passing enough state to the server for several operations to be executed at once. This pattern can be equally effective in Ajax, given that it is better to do one call that transmits a lot of information than a dozen small ones.

Another popular technique is to move more complexity onto the client. Given that Ajax clients, in general, occupy a single physical page, a fair amount of state can be kept on the client via caching. Caching is probably the most important technique in Ajax development and, where possible, should be exploited to optimize communications with the server.

Whichever technique you use, it will pay dividends in the long run, and the server infrastructure guys will love you for it. The users of your application will also appreciate its faster response times and interactivity.

Summary

In this chapter, you learned about the extensive range of adaptive Ajax tags that Grails offers and how to apply them to give your gTunes application a more usable interactive interface. On this particular journey, you also explored advanced Grails development, learning a lot more about how controllers, templates, and the render method function in combination.

In the past few chapters, you've been very much involved with the web layer of Grails in the shape of controllers, GSP, tag libraries, and Ajax. However, everything you have looked at so far has used completely stateless communication. In the next chapter, you'll look at how Grails supports web flows for rich conversations that span multiple pages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset