This is yet another GroovyMag article.
This one was published in September 2009 and is still running on Stax (just follow that last link to learn more).
Since the time of writing, Microsoft has announced that it's joining the SVG working group (about bloody time!).
The Groovy Internet Mood Meter in SVG
Embedding and updating scalable vector graphics in XHTML
XML is a very effective tool promoting data interchange between disparate systems. It's a useful weapon that is slowly helping to quell the numerous "format wars" that have been fought throughout the history of IT. One of the
ongoing but lesser known skirmishes concerns graphics. The plethora of current formats is confusing, costly, and simply not appropriate for the needs of advanced web applications. With most current formats, intricate drawings are far too
large, difficult to produce and hard to use. Anyone who has battled with HTML image maps knows that the traditional image formats provide almost no opportunity for sophisticated scripting and interactivity. Scalable Vector Graphics (SVG)
is an application of XML that solves all of the previously mentioned problems and also provides an open, standards-based alternative to the common graphics formats in use today. GrIMMiS is a very small Grails application I have cooked up
that lets me use AJAX to dynamically update portions of an SVG drawing-and bring a peaceful end to the format wars.
A brief overview
This little dish has a bunch of tasty ingredients! There's Grails, JavaScript (plain and using Grails' support for prototype/AJAX), SVG (with a small amount of declarative animation), and XHTML, all rolled into one neat little savory
package.
The Groovy Internet Mood Meter in SVG (GrIMMiS) itself is intentionally pretty simple: when you click on the smiley that best reflects your mood you influence the meter value, which is updated appropriately. The meter also periodically
updates to reflect the ongoing cumulative mood of "the Internet." With each update, GrIMMiS also retrieves a pithy saying or joke that varies depending on the overall mood.
Figure 1 shows GrIMMiS in all its glory running in Opera 10 beta 2, which currently (to me) seems to offer the best SVG support.

Figure 1: The GrIMMiS application
GrIMMiS mirrors the basics of a real SCADA HMI (Supervisory Control and Data Acquisition Human-Machine Interface) application that I developed for a client a long time ago-even before Jesse James Garrett's coined the term 'AJAX' in the
shower[1]. My original application was written in Java with plain JavaScript and had a fair amount of messy XML processing. In writing GrIMMiS, I wanted to see how Groovy and Grails would improve things. Of course, I found that the two
technologies made things a lot simpler.
The SVG image
In preparation for the upcoming HTML5 standard, most modern browsers are supporting SVG more and more faithfully with each release. SVG is now available as a standard feature in Opera, Firefox, Safari and Chrome. It is found everywhere
from JEE applications to mobile phone displays and frequently crops up in unexpected places. For example, much of the KDE4 Desktop's user interface has been styled with SVG, and SVG can also be used to render UML diagrams in Oracle's
JDeveloper IDE.
Notably absent is Microsoft's Internet Explorer [2]. In My (not-so) Humble Opinion, Redmond should be thoroughly ashamed of itself; SVG support is by far the most requested new feature for IE yet the community's pleas have continually
fallen on deaf ears. Adobe also should be ashamed of their "bait and switch" behavior in supplying the 'anointed' SVG plugin for IE and then unceremoniously pulling the plug, leaving the community high and dry with no alternative but to
move to proprietary systems like flash/flex, and indirectly opening the door for Silverlight. Even worse, Adobe still supports SVG in applications like Adobe Reader and many other products but will no longer provide that support for IE.
All is not lost however: there is evidence that Google is tiring of waiting for Microsoft to support SVG on IE and has launched its own SVG project called "SVG Web" [3]. Examotion is also developing the RENESIS plugin, which is intended to
be a re-engineering of the Adobe plugin [4].
Enough background chit-chat! Listing 1 shows the source for the meter portion of the image that is shown in Figure 1.
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
width='600'
height='250'>
<style type="text/css">
text {
font-size:12px;
text-anchor:middle;
font-family:sans-serif;
}
.bold {
font-weight:bold;
}
</style>
<g id='meterGroup'>
<circle cx="400" cy="25" r="10"
fill="white" stroke="black" stroke-width="1">
<animateColor attributeName="fill"
attributeType="CSS" values="white;white;blue;white"
keyTimes="0;0.70;0.75;1.0"
repeatCount="indefinite"
dur="5s" />
</circle>
<path d="M 300,180 L 550,180 A 248 248 1 0 0 400,25"
fill="crimson" stroke="black" stroke-width="1" />
<path d="M 300,180 L 400,25 A 220 220 1 0 0 200,25"
fill="lightGray" stroke="black" stroke-width="1" />
<path d="M 300,180 L 200,25 A 248 248 1 0 0 50,180 L 300,180"
fill="RoyalBlue" stroke="black" stroke-width="1" />
<g id='pointerGroup' transform="translate(300,180)">
<g id='pointerRotateTransform' transform="rotate(-90)">
<line x1='0' y1='0' x2='150' y2='0'
fill="yellow" stroke="yellow"
stroke-width="15" stroke-linecap="round" />
</g>
</g>
<circle cx="300" cy="180" r="20" fill="black" stroke="darkGray"
stroke-width="3px" />
<text x="300" y="220" id="reading" class="bold">90.0</text>
<text x="300" y="240" id="joke"></text>
</g>
</svg>
Listing 1: The SVG Meter Image source code
There are a number of points of interest in this code (it sounds a little weird talking about the source-code for an image, doesn't it, but that's what we have here).
First, SVG is an application of XML and therein lies one of its great strengths: there are many XML tools and techniques available for processing XML, and used with SVG as well, making it easy to create and process images
programmatically.
Secondly, the ability to use Cascading Style Sheets (CSS) is another great strength. Given that SVG is a first-class member of the large set of web technologies promulgated by the World Wide Web Consortium (W3C), one would expect
nothing less than that CSS and (as we will see) JavaScript work without hassle.
Various elements of the drawing are self explanatory: circle, line, text . Note that several elements have been given unique values for their id attribute: this will simplify later processing.
The g element introduces a group. The important point of interest here is that the g element allows the specification of a transform attribute. Transforms can be used to rotate, translate, scale and skew any portion of an image. In this
image, the line element within the 'pointerGroup' g element is initially defined as if it were at position 0,0 and pointing straight to the right, and then translated and rotated into its proper place and orientation. Although this may
seem strange at first, this facility allows complex images to be built up from independent sub-parts that are 'munged' into place as needed. The power of all this becomes apparent when one realizes that the various sub-parts may have been
produced in isolation, or perhaps generated as the result of a data query or other program action, without reference to any particular subsequent use. In this way, SVG is designed to make it easy to put together reusable 'clip-art'-style
libraries.
The three path elements draw the three wedges that make the dial of the meter. The key point of interest here is the mini "turtle graphics-style" language embedded into the d (path data) attribute. For example, the first line element
decodes as:
M absolute moveto x, y
L absolute lineto x, y
A absolute arc rx ry x-axis-rotation short-or-long-way-flag clock-anticlock-flag x, y
The first circle makes use of the declarative animation provided by SVG. This is actually another W3C-originated technology called Synchronized Multimedia Integration Language (SMIL; pronounced "smile"). The animation rule declared here
periodically manipulates the circle's CSS fill property to provide a 'busy' indicator: every 5 seconds the circle will flash blue for 1.2 seconds. Not every browser currently handles SVG declarative animation; for instance, most fairly
recent versions of Opera do but Firefox 3.5.1 does not.
The final point to note is the rule that defines z-order layering of the image elements: textually later elements appear on top of earlier ones.
If all this XML still leaves you unconvinced, consider this: the SVG document is less than 1.5Kb in size, while the equivalent PNG image is about 16Kb; more complex diagrams save even more. When you consider that Yahoo [5] estimated
that roughly 80% of its web traffic involves image downloads, savings of one or two orders of magnitude start to look really attractive.
The XHTML 'Container' GSP page
In GrIMMiS, the SVG image source shown in Listing 1 is itself embedded into an XHTML document. This is the first true ingredient of the GrIMMiS Grails application because the XHTML is produced via a Groovy Server Pages (GSP) page,
shown as Listing 2.
< %@ page contentType="application/xhtml+xml" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en" lang="en">
<head>
<title>GrIMMiS</title>
<link rel="stylesheet" type="text/css" href="<g:resource dir='css' file='grimmis.css' />" />
<g:javascript library="prototype"/>
<script type="text/javascript">
//<![CDATA[
var POLL_TIME = 5;
var PRTEL = 'pointerRotateTransform';
var JEL = 'joke';
var REL = 'reading';
function init() {
new PeriodicalExecuter(function(pe) {
new Ajax.Request('<g:createLink controller="grIMMiS" action="poll" />', {
method: 'get',
onSuccess: onSuccessFunction
});
}, POLL_TIME);
}
function onSuccessFunction(e) {
var XML = e.responseXML
var rel = XML.getElementById(REL);
var n = $('meter').suspendRedraw(500);
var reading = $(REL);
reading.parentNode.replaceChild(rel, reading);
var prtel = XML.getElementById(PRTEL);
var pointerRotateTransform = $(PRTEL);
pointerRotateTransform.parentNode.replaceChild(prtel, pointerRotateTransform);
var jel = XML.getElementById(JEL);
var joke = $(JEL);
joke.parentNode.replaceChild(jel, joke);
$('meter').unsuspendRedraw(n);
}
//]]>
</script>
</head>
<body onload="init();">
<h1>GrIMMiS-the Groovy Internet Mood Meter in SVG</h1>
<table class="layout centered">
<tr>
<td colspan="3">How is the Internet feeling today?</td>
</tr>
<tr>
<td colspan="3">
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
class="svg"
id='meter'>
<g:include controller="grIMMiS" action="svg"></g:include>
</svg>
</td>
</tr>
<tr>
<td colspan="3">How are <em>you</em> feeling today?</td>
</tr>
<tr>
<td><g:remoteLink controller='grIMMiS' action='sad' onSuccess='onSuccessFunction(e);'>
<img src="images/nicubunu_Smiley_Cry.png" class="button" title="Sadder"/>
</g:remoteLink>
</td>
<td><g:remoteLink controller='grIMMiS' action='neutral' onSuccess='onSuccessFunction(e);'>
<img src="images/nicubunu_Smiley_Puzzled.png" class="button" title="Neutraller"/>
</g:remoteLink>
</td>
<td><g:remoteLink controller='grIMMiS' action='happy' onSuccess='onSuccessFunction(e);'>
<img src="images/nicubunu_Smiley_Laugh.png" class="button" title="Happier"/>
</g:remoteLink>
</td>
</tr>
</table>
<div class="footer">transentia pty. ltd.; 13/July/2009</div>
</body>
</html>
Listing 2: The Container XHTML GSP page
Listing 2 has a number of ingredients worth noting.
Since this is a GSP, I am able to use Grails taglibs like <g:resource>, <g:createLink> and <g:remoteLink> to reference resources (such as grimmis.css) in a nice neat way, and to cleanly use AJAX to talk back to the
GrIMMiS controller's various actions. This allows me to manipulate the remote meter values or to update the local SVG drawing.
Note that the page retrieves the actual SVG image via the GrIMMiS controller's svg closure and includes it inline (by using <g:include>), rather than load it via a separate HTTP connection. This makes slightly more efficient use
of the network, and having the image included inline in this fashion gives a unified Document Object Model (DOM). This greatly simplifies DOM processing and helps with cross-browser compatibility.
I have also made use of SVG's ability to have nested SVG elements. This lets me build the actual image-without worrying how it might eventually be used in the page-while applying SVG-specific styling, and a unique ID to the outer SVG
element.
I am using Grails' prototype library for AJAX support and since I need the meter to regularly update to show the prevailing "mood out there," I am hand-crafting a small bit of JavaScript/prototype code: using Ajax.PeriodicalExecutor in
conjunction with a Ajax.Request for regularly (every POLL_TIME seconds) polling back to the GrIMMiS controller's poll closure.
Polling is initiated by the client's browser as it executes the XHTML page's onload handler.
The JavaScript callback function 'onSuccessFunction' is the meat of this little recipe. It is called after every successful AJAX operation to process the retrieved data. As expected, The GrIMMiS controller sends a small XML document to
the client in response to each AJAX request. onSuccessFunction takes this incoming document, extracts the relevant fragments from it (those that represent new values for the elements of the drawing that are to be updated), locates the
now-superseded original fragments in the SVG document and replaces them. The meter is automatically redrawn after being updated.
Note how onSuccessFunction uses the suspendRedraw/unSuspendRedraw functions to turn document redraw on and off during updates; this can greatly improve browser performance.
onSuccessFunction is slightly trickier than I would really like: it is able to use prototype's $('element') syntax for addressing elements in the larger HTML document (and thus, the embedded SVG as well), but must use plain
JavaScript to work with the elements of the XML fragment received from Grails. This is slightly painful, but bearable, and the various workarounds that exist (such as adding the received document's elements into a hidden div and then
shuffling them into place) may be worse than the cure in terms of readability.
The final point of interest on this page concerns the use of Grails' <g:remoteLink> tag to handle AJAX-style interaction with the controller when activated by the smiley-face image buttons that increase/decrease/'neutralize' the
meter's value.
The Grails controller
Listing 3 shows the single, very simple controller which is responsible for serving the SVG image and handling any interactions with the client browsers.
class GrIMMiSController {
def drawingService
def index = { redirect(action: svg) }
// get full document
def svg = {
render(text: drawingService.svg(), contentType: 'image/svg+xml', encoding: 'UTF-8')
}
// get changes for document
def poll = {model = null ->
render(text: drawingService.poll(), contentType: 'text/xml', encoding: 'UTF-8')
}
def happy = {
drawingService.happier()
}
def sad = {
drawingService.sadder()
}
def neutral = {
drawingService.neutraller()
}
// this interceptor means that happy, sad and neutral don't have to explicitly call poll()...DRY goodness
//
// must be elaborated after def poll = { } ... otherwise poll will be null
def afterInterceptor = [action: poll, only: ['happy', 'sad', 'neutral']]
}
Listing 3: The Grails controller
This controller is a thin adapter that mostly delegates to the injected drawingService instance.
The major point of interest here is the use of an afterInterceptor. Without this interceptor, each happy, sad and neutral smiley-face image buttons would need to look something like:
def action = {
// body
poll()
}
With this interceptor in place, these actions are much simpler and more DRY.
Note that the poll action can be invoked either as a result of an incoming HTTP request as an action in its own right or as the callback closure associated with the defined afterInterceptor. In the former case, no parameter is passed
into the closure, while in the latter case a parameter is passed in by the actual interceptor code (although it isn't used in this example). The use of a default parameter value helps avoid redundancy.
The single tricky point here is that afterInterceptor must be declared after poll has been fully declared, otherwise poll will be null at the point of declaration, and Grails will complain when one of the designated actions is
invoked.
The Grails service
Most of the meat of the Grails application lives in the service class and a couple of small subsidiary classes.
Listing 4 shows the DrawingService.groovy file.
import groovy.util.XmlNodePrinter
class DrawingService {
boolean transactional = false
static scope = "singleton"
def jokeService
final MIN = 0
final MAX = 180
final QMAX = MAX / 4
final HMAX = MAX / 2
final BOTTOM_QUAD = MIN..<QMAX
final LOW_QUAD = QMAX..<HMAX
final HIGH_QUAD = HMAX + 1..<(HMAX + QMAX)
final TOP_QUAD = (HMAX + QMAX)..MAX
final mood = new Mood(range: MIN..MAX)
def document
def pointerRotateTransform
def joke
def reading
def initialise = {file ->
def start = System.currentTimeMillis()
log.debug "Initialise START: $file"
log.debug "Parsing..."
this.document = new XmlParser().parseText(new File(file).text)
log.debug "Isolating important elements for updating..."
this.pointerRotateTransform = document.g.g.g.find { it.'@id' == 'pointerRotateTransform' }
log.debug "Found pointerRotateTransform: $pointerRotateTransform"
this.joke = document.g.text.find { it.'@id' == 'joke' }
log.debug "Found joke: $joke"
this.reading = document.g.text.find { it.'@id' == 'reading' }
log.debug "Found reading: $reading"
log.debug "Initialise END; (${System.currentTimeMillis() - start}ms)"
}
// get full document
def svg = {->
synchronized (this) {
toSVG document
}
}
def poll = {->
synchronized (this) {
joke.value = message()
reading.value = fint(mood.value.current)
pointerRotateTransform.'@transform' = "rotate(-${fint(MAX - mood.value.current)})"
toSVGFragment pointerRotateTransform, joke, reading
}
}
def happier = {->
synchronized (this) {
mood.happier()
}
}
def sadder = {->
synchronized (this) {
mood.sadder()
}
}
def neutraller = {
synchronized (this) {
mood.neutraller()
}
}
private fint = { v ->
String.format("%d", (int)v)
}
private message = {->
switch (mood.value.current) {
case TOP_QUAD: return "Wow...what's in the collective waters today :-)"
case HIGH_QUAD: return "The world seems to be going swimmingly...so far, anyway."
case HMAX: return "Keep on in that Groove, guys."
case LOW_QUAD: return "You need a joke: ${jokeService.joke()}"
case BOTTOM_QUAD: return "Oh dear! We all need a group session with a psycho-therapist, it seems."
default: return "???"
}
}
private toSVG = {n ->
def writer = new StringWriter()
def xnp = new XmlNodePrinter(new PrintWriter(writer))
xnp.setNamespaceAware true
xnp.setPreserveWhitespace false
xnp.print(n)
writer.getBuffer().toString().trim()
}
private toSVGFragment = {Object ... elems ->
def s = new StringBuilder("<frag>")
elems.each { s << toSVG(it) }
s << "</frag>"
s
}
}
class Mood {
private final BIG_JUMP = 20
private final SMALL_JUMP = 10
private final randFromOne = RandomUtils.&random.curry(1)
private final bigRandom = randFromOne.curry(BIG_JUMP)
private final smallRandom = randFromOne.curry(SMALL_JUMP)
final value
Mood(m) {
value = new RangedValue(range: m.range)
}
def happier = {->
value.current += bigRandom()
}
def sadder = {->
value.current -= bigRandom()
}
def neutraller = {->
// force value toward center
switch (value.current) {
case { it > value.middle }: value.current -= smallRandom(); break
case { it < value.middle }: value.current += smallRandom(); break
default: break /* do nothing */
}
}
}
class RangedValue {
final range
final middle
def current
RangedValue(m) {
this.range = m.range
this.middle = this.current = mid(m.range)
}
public void setCurrent(v) {
switch (v) {
case { it > range.to }: current = range.to; break
case { it < range.from }: current = range.from; break;
default: current = v; break
}
}
private static mid = {r ->
return (r.to - r.from) / 2
}
}
Listing 4: The Grails service and associated classes
This service class has a number interesting points worth noting.
The initialise closure is defined to permit configuration of which SVG file to use via the Grails bootstrap, as shown in Listing 5.
import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
class BootStrap {
def drawingService
def init = {servletContext ->
def file = "${AH.application.parentContext.getResource('images/').file}${File.separatorChar}GrIMMiS.svg"
drawingService.initialise file
}
def destroy = {
}
}
Listing 5: The modified Grails Bootstrap.groovy class
Note the use of Grails' ApplicationHolder class, which lets us discover the location of the images folder. This is much better than using some hard-coded path.
The initialise closure also pre-resolves the various dynamic elements of the drawing so that they can be easily manipulated by other code later. I'll be the first to agree that this is a micro-level performance optimization, but one
that I found became important as the size of an image (and also the number of dynamic elements) increased.
As the saying goes [6]: "Nothing makes you want Groovy more than XML", and initialise lets you see Groovy's superb XML-handling capabilities first-hand. For example, the following navigates through the SVG document to find the unique g
element with id attribute value equal to 'pointerRotateTransform':
document.g.g.g.find { it.'@id' == 'pointerRotateTransform' }
The first of the two of the major methods in Listing 4 is svg. This method is responsible for writing the entire SVG document as a string (which is of course eventually returned to the client browser as part of the XHTML document
produced by the GSP shown in Listing 2).
Note that svg always writes the most up-to-date state of the SVG document so that a new client retrieves an accurate representation of the meter's value right from the start. The alternative approach would be to write the original
(as-stored-on-disk) image document to the client and then wait for a refresh to occur. Although possibly simpler and less server-side resource intensive, I have found this alternative to be a generally unsatisfactory approach. It becomes
even more so as the image increases in size and complexity (remember that GrIMMiS after all is only a trivial example application), or the drawing poll rate becomes longer.
The second major method in the service is poll. This method is responsible for updating the underlying SVG document to reflect the changing mood of "the Internet," as well as for generating an XML document fragment containing only the
updated elements of the image. An example of the document fragment produced is shown here:
<frag>
<g xmlns="http://www.w3.org/2000/svg" id="pointerRotateTransform" transform="rotate(-136)">
<line x1="0" y1="0" x2="150" y2="0" fill="yellow" stroke="yellow" stroke-width="15" stroke-linecap="round"/>
</g>
<text xmlns="http://www.w3.org/2000/svg" x="300" y="240" id="joke">
Oh dear! We all need a group session with a psycho-therapist, it seems.
</text>
<text xmlns="http://www.w3.org/2000/svg" x="300" y="220" id="reading" class="bold">
44
</text>
</frag>
The method poll makes use of a simple helper method called toSVGFragment. If you look at toSVGFragment, you can see that I have made good use of Groovy's simple varargs facility to define a method capable of handling an arbitrary number
of SVG elements in one invocation. This again helps the DRY-ness of the code and aids development and maintainability: the need to render an additional dynamic element is easily met by simply adding that element to the parameters passed
toSVGFragment. There is no need to take alternative, less direct, approaches such as passing parameters as lists or maps, or to define toSVGFragment to take some arbitrary number of (mostly default) parameters.
DrawingService makes effective use of Groovy's ranges. Listing 6 (excerpted from Listing 4) shows how I split the range from 0 to 180 into four equal-sized, non-overlapping subranges that corresponding to the angle of the meter's
pointer. These subranges are then used in conjunction with Groovy's excellent switch statement to determine an appropriate message to display.
final MAX = 180
final QMAX = MAX / 4
final DMAX = MAX / 2
final BOTTOM_QUAD = 0..<QMAX
final LOW_QUAD = QMAX..<DMAX
final HIGH_QUAD = DMAX + 1..<(DMAX + QMAX)
final TOP_QUAD = (DMAX + QMAX)..MAX
private message = {->
switch (constrain(mood)) {
case TOP_QUAD: return "Wow...what's in the collective waters today :-)"
case HIGH_QUAD: return "The world seems to be going swimmingly...so far, anyway."
case DMAX: return "Keep on in that Groove, guys."
case LOW_QUAD: return "You need a joke: ${jokeService.joke()}"
case BOTTOM_QUAD: return "Oh dear! We all need a group session with a psycho-therapist, it seems."
default: return "???"
}
}
This excerpt also shows how the JokeService is used. Listing 6 shows this trivial class.
class JokeService {
boolean transactional = false
private jokes = [
/Q: What's yellow and dangerous? A: shark-infested custard!/,
'The only culture in Australia is found in its youghurts.',
'When someone emigrates from NZ to Oz, the IQ of both countries goes up.'
]
def joke = {->
return jokes[RandomUtils.random(0, jokes.size())]
}
}
Listing 6: The JokeService class
Of course, a more sophisticated implementation would pull jokes from a database or perhaps an Internet WebService.
Fancy some curry? In keeping with the slightly gastronomic theme to this article, the Mood class makes use of a technique called currying.
Currying is a functional programming technique that lets you create a new closure based on an existing closure where one parameter is pre-loaded and fixed with a given value (there is a long and very boring formal definition of currying
on Wikipedia [7], but this will do).
Rather than having calls to the general-purpose RandomUtils.random closure dotted around my code using (nearly) the same set of parameters at each call, you can see how I successively fix parameters in place to obtain a group of
specialized closures perfectly adapted for Mood's specific needs:
final randFromOne = RandomUtils.&random.curry(1)
final bigRandom = randFromOne.curry(BIG_JUMP)
final smallRandom = randFromOne.curry(SMALL_JUMP)
The specialized closures are used throughout the DrawingService class. This is a very powerful technique that can help simplify your code and improve its readability.
Time for a bit of pop philosophy! The Mood class embodies GrIMMiS' business rules, such as they are. The philosophy embodied here is that although one is easily capable of saying "I am emotional," it is extremely hard to definitely
quantify emotion. Mood thus uses a random number for each operation. When a user hits the happy or sad smiley button, a value taken from a large random range is used (the thinking is that client X could be very happy, while client
Y only mildly sad). A user that presses the neutral smiley face is presumed to be less subject to wild "mood swings" and so the value used is taken from a smaller random range.
One more point of interest in this code is the use of Groovy's '&' operator: this takes a reference to a method and treats it as a closure (so that it can be subsequently curried, for instance, as shown in the above excerpt).
For completeness, Listing 7 shows RandomUtils, a tiny helper class in src/groovy.
public class RandomUtils {
private static final rg = new Random()
public static int random(off, lim) {
off + rg.nextInt(lim)
}
}
Listing 7: The RandomUtils class
The following snippet taken from the RangedValue class shows how I have used an explicit setter for the derived property current (the Groovy automatically-supplied getter continues to be used as normal). The supplied setter method
ensures that current is maintained within the bounds of the range defined for it at construction.
def current
public void setCurrent(v) {
switch (v) {
case { it > range.to }: current = range.to; break
case { it < range.from }: current = range.from; break
default: current = v; break
}
}
Both JokeService and DrawingService are marked as non-transactional. This is clearly appropriate and may help to conserve server-side resources.
The final point to note from Listing 4 concerns the various synchronized blocks that you will see in the code. In contrast to a controller, which typically has many active instances (usually one instance per request), a service by
default is a singleton: there is only one instance regardless of how many controllers are trying to use it. This is quite appropriate here: the drawingService should only maintain a single SVG drawing, regardless of however many clients
are using GrIMMiS. The singleton nature of drawingService does mean that many operations (such as updating the current mood value, or modifying the server-side SVG diagram to reflect that mood) should be carried out in a serialized manner
to avoid possible mis-correspondence of mood value and rotation transform value or message.
Wrapping Up
I hope you enjoyed taking a look at the recipe for this little entrée.
Recall that I started to make this dish because I wanted to see if Groovy and Grails would make it easier to build an application similar to one I built for a client several years ago using plain Java. It is clear that Groovy's superb
XML support and simpler file handling has improved things. It is also clear that features like varargs and currying have let me produce much more maintainable code. By providing a clean web-based environment, with interceptors and
effective support for JavaScript libraries (such as prototype), Grails has made it possible for me to write much better code than before. In general, even when taking into account the significantly smaller size and reduced
capabilities, this application has a cleaner, more comprehensible feel to it.
I wish I'd had these "Gr8 technologies" available to me many years ago!
Learn More
- The origin of the term 'AJAX', http://en.wikipedia.org/wiki/Ajax_(programming)
- SVG support matrix for the various browsers, http://www.codedread.com/svg-support.php
- Google SVG Web, http://code.google.com/p/svgweb/
- RENESIS SVG Plugin for IE, http://www.examotion.com/index.php?id=product_player
- Yahoo User Interface Blog, http://yuiblog.com/blog/2006/11/28/performance-research-part-1/
- Stuff I've learned recently, http://kousenit.wordpress.com/2008/03/12/nothing-makes-you-want-groovy-more-than-xml/
- Wikipedia: Currying, http://en.wikipedia.org/wiki/Currying