Moving My Google Maps Mashup to Grails, part one

The Google Maps API is easy to use. The basic idea is to create a Map2 object (the former GMap2, now known as google.maps.Map2) and add Marker objects to it. The Marker objects then use InfoWindow objects to display all the information for that marker, which can either appear on load, or you can set up an event (like a click) to make the info display. I also like the Polyline objects, especially because you can make them follow geodesics.

While there are many good tutorials about Google Maps on the web, my favorite reference is Scott Davis’s book Google Maps API, v2. I purchased the eBook version at Pragmatic Programmer and really like it.

Since the API is in JavaScript, the key issue for me is how to get my location data into the code. It’s all in a database, but it’s also considered poor design to talk directly to a database from the view. When I wrote my app originally in Java, I did the normal process of building a data access layer in front of the database and then using a servlet to access it, turn the locations into Java objects, and then pass the whole lot to the view as request attributes. That’s all well and good, but of course JavaScript code can’t read Java objects directly.

Dave Crane has a good discussion of this issue in his book Ajax in Action. (Of course it’s a good discussion — everything in that book is good. :)) I recently also picked up his Prototype and Scriptaculous in Action book, and it too talked about the mismatch problem.

Crane lists the available options as

  • Content-centric, where the back-end code returns already-formatted HTML which a web page can render,
  • Script-centric, where actual JavaScript code is returned and executed by the browser, and
  • Data-centric, where the information is returned in some format that both sides can understand.

For my situation, the data-centric approach is really the only option. I’m not returning information that I want to render directly. My hope is for Google Maps to do all the rendering. For the same reason, I don’t want to return actual code, because that wouldn’t simplify anything.

I’m going with the data approach. Since JavaScript doesn’t understand either Java or Groovy objects, I need to select an intermediate format for the data. That also means I need to be able to generate the format from the Java or Groovy objects and then parse it in JavaScript.

As Crane points out, the major options are either XML or JSON.

I do remember back when XML was popular. I thought it was pretty cool, too, during that late 90’s and the first few years of this century. But XML gets parsed into DOM trees, and the DOM access API’s in JavaScript are bulky and cumbersome.

(But not as ugly as those in Java. I’ve always been amazed how badly Java interacts with XML, especially now that seemingly every business must, by law, build a service oriented architecture implementing web services.)

Groovy’s approach to XML is refreshingly easy, but again it doesn’t really help me here. I need to get my latitudes and longitudes into the constructor of Google’s LatLng class, which is what Marker uses to draw at a particular location.

The other intermediate format possibility is JSON. I really like JSON. It’s easy, short, and quite flexible. I wonder if JSON had been around when XML first became available if we’d all be using it instead.

Before I get to that, though, I have to mention another possibility that I actually used in my original implementation. There I relied on JSP tags, which generated JavaScript as though it was any other set of strings.

Assuming my courses all contain locations, and that each location has a latitude and a longitude, what I did was:


<c:forEach var="course" items="${courses}">
    loc = new google.maps.LatLng(
        "${course.location.latitude}","${course.location.longitude}");
    // etc...
</c:forEach>

and went on from there. The benefit to this approach is that I could access the courses directly as Java objects, using the normal dot notation to get where I needed to go.

The downside to this approach is that it makes for some truly ugly JavaScript. Note that I don’t have the word “var” in front of my (JavaScript) loc variable above. I can’t, because my <c:forEach> loop is going to generate a new copy of that line — and every other line in the loop — for every single course. I also didn’t try to store the values as I went along, which I suppose I could have done by declaring an array ahead of time, assigning each LatLng to an array at the “i”th index location, and manually incrementing the index. The effect is that I’m writing out each iteration of every loop.

I have to say, though, that the approach did work. I saved myself the difficulty of translating from Java objects to anything else, at the expense of repeating about a dozen lines of code over and over again for every course.

That can’t be the “right” way to go. Since I was porting to Grails anyway, I investigated what tools it (and, of course, Groovy) brought to the task.

Chapter 9 of Graeme Rocher’s book The Definitive Guide to Grails discusses the Grails tags available for Ajax applications. He goes through tags <g:remoteField>, <g:remoteLink>, and <g:formRemote> in some detail. Unfortunately for me, all three of those tags have an attribute called “update” which indicates which <div> element will contain the resulting data.

I don’t want to update a <div> element. I want numbers back.

Incidentally, in order to get a better sense of what was going on, I used those tags and looked at the resulting generated HTML. Not surprisingly, the tags all created Ajax.Updater calls in Prototype, my library of choice. What I wanted was an Ajax.Request, from whose results I could extract the text data and parse away.

Since the DGG book is getting a bit dated (already! I hope a good revision when Grails goes to 1.0 in October is already in the works), I checked the tag reference at the Grails web site. In addition to the above three tags, it also listed <g:remoteFunction> and <g:submitToRemote>, but neither of those were necessarily appropriate either.

The fact is, however, the data I need is already in the web page. It’s just in the form of a collection of instances of the Groovy classes called Course and Location, which are part of my domain model. All I need to do is to convert them to JSON and I’m off to the races.

Enter the Converters plug-in project for Grails, which is even built into Grails 0.6. The Converters plugin gives a convenient (according to the web site, YMMV) way to convert your “domain objects, maps, and lists to JSON or XML very quickly.”

The web page has examples on it, but I must admit I found them rather confusing. It’s entirely possible I’m missing something obvious. For example, I’m still not sure how it would help me to do a “render Course.get(0) as JSON” in a controller. I guess in my page I could set up a normal Ajax.Request myself to call an action implemented that way, but I’m not sure that’s they way the example is intended to be used.

I also got messed up when I tried to convert my collection to JSON, rather than a single object. The line above,

render Course.get(0) as JSON

works just fine, but

render courseList as JSON

throws exceptions. Eventually I wound up going to

render [crs:courseList] as JSON

which did the job, at the price of introducing a variable I don’t need. Today, though, I did some poking around on the Grails User mailing list archive at Nabble and found that

def data = courseList as Course[]
render data as JSON

might work instead, but I haven’t yet tried that out.

What I ultimately did was to go into my GSP page and use the converter’s encodeAsJSON() function. My page contains:


function initialize() {
    map = new google.maps.Map2($("map"));
    var homeJSON = ${Location.findByCityAndState('Marlborough','CT')
        .encodeAsJSON()};
    home = new google.maps.LatLng(homeJSON.latitude,homeJSON.longitude);
    map.setCenter(home, 4);

    // Transform the course list to a JSON object
    //  whose 'crs' property is the list of individual courses
    var coursesJSON = ${[crs:courseList].encodeAsJSON()};

    // Do the same with the locations
    var locsJSON = ${[locs:locationList].encodeAsJSON()};
    var courseArray = coursesJSON.crs;
    var locsArray = locsJSON.locs;

    processCourses(courseArray,locsArray);

    map.enableScrollWheelZoom();
    map.addControl(new google.maps.LargeMapControl());
    map.addControl(new google.maps.MapTypeControl());
    map.addControl(new google.maps.OverviewMapControl());
}
google.setOnLoadCallback(initialize);

That brings me to my other difficulty with using the JSON builder. My location objects are not normally embedded in my course GSP pages. I expected to get to the locations by traversing from the course to its location. The problem is, if you look at the generated JavaScript, the first Course object becomes

{
      "startDate": "2005-04-04 00:00:00.0",
      "title": "Intro Java with WSAD",
      "class": "Course",
      "endDate": "2005-04-08 00:00:00.0",
      "client": 1,
      "location": 1,
      "id": 1,
}

which means its location is just a foreign key value, not an actual object. I can’t traverse from Course to Location in JSON form. Since I want information from both objects (i.e., the Course title and dates, and the Location’s latitude and longitude), I had to make both sets of data available to my view.

(Also, note that the date is now firmly a string. I tried parsing it using JavaScript’s Date class, but no luck.)

That, at least, was easy. My CourseController.groovy class originally had


def list = {
    if(!params.max)params.max = 10
    [ courseList: Course.list( params )]
}

in it, and now it has


def list = {
    if(!params.max)params.max = 10
    [ courseList: Course.list( params ), locationList: Location.list() ]
}

I had to be sure not to pass the params argument to Location.list(), because while I want the course listing table to have pagination, I don’t know which locations I’m going to need for the courses so I have to send them all. It also forces me to traverse the relationship myself. In my processCourses function, I have


function processCourses(courseArray, locsArray) {
    // === Plot the markers ===
    for (var i = 0; i < courseArray.length; i++) {
        var c = courseArray[i];
        var loc = locsArray[c.location - 1];
        var latlng = new google.maps.LatLng(loc.latitude,loc.longitude);
        var label = "<b>" + c.title + "</b><br />" +
            "<em>" + loc.city + ", " + loc.state + "</em><br />" +
            c.startDate.split(' ')[0] + " -- " +
            c.endDate.split(' ')[0];

        var marker = createMarker(latlng, label);
        map.addOverlay(marker);

        var polylineOptions = {geodesic:true};
        var polyline = new google.maps.Polyline(new Array(home,latlng),
             "#ff0000",2,0.5,polylineOptions);
        map.addOverlay(polyline);
    }
}

and you can see that my location comes from selecting the proper element out of the locations array, based on the index value that comes from course.location. It’s not pretty, but it does seem to work.

This leaves me with two issues that have kept me from putting my application on the web yet.

  1. By default, Grails applications let the user add, edit, and remove elements as well as list them. I have to add some login mechanism before I expose that functionality. 🙂 Jason Rudolph in his Getting Started with Grails book used an interceptor for that. I’ll no doubt start there.
  2. Rendering the course as a JSON object embeds all of its details inside the JavaScript.

That’s a much bigger problem. My course objects have references to clients and even to rates in them, and I’d just as soon not expose that to anyone capable of doing a “view source” on the page (security by obscurity, indeed). I’m not exactly sure how I’m going to handle that.

My first thought is to create some kind of “narrow” interface to course objects that would expose only the fields I need to show. I’m going to try creating a class called, say, CourseMapData, which will consist of


class CourseMapData {
    String title
    Date startDate
    Date endDate
    String city
    String state
    double longitude
    double latitude
}

which will be populated from a Course object and its associated Location, send that to the view and convert it to JSON. I think that will work, but it’ll mean adding a new Groovy class that isn’t technically a domain object from the Grails point of view. I haven’t done that before, so that’s a good learning opportunity. Of course, I’ll also have to put in a method to do the conversion, too, and that method isn’t exactly a controller action, so I’m not sure where it goes, either.

I guess that’s part of the problem with eliminating formal DAO classes. Grails supplies finders automatically, which is great. I loved being able to use Location.findByCityAndState() without having to write anything. But I would normally put my conversion method in the Course DAO, and now I’m not so sure what to do.

When I get those issues worked out, I’ll no doubt post a “part two” entry here.

3 responses to “Moving My Google Maps Mashup to Grails, part one”

  1. Hi Ken,
    The interceptor approach will certainly work for simple security. If your needs become any more complex, I’d encourage you to have a look at the Grails Acegi plugin.

    http://www.dcs.shef.ac.uk/~hsun/downloads/grails-acegi-0.1.zip
    http://www.dcs.shef.ac.uk/~hsun/downloads/Custom-Grails-Acegi-plugin-guide.doc

    It’s a cinch to install, and it gives you both authentication and authorization in an incredibly straightforward and easy-to-use package.

    Cheers,
    Jason

  2. I was going to use the interceptor approach, but now I think I’ll try the Acegi plugin, especially when you gave me the links and everything. I’ve managed to use Spring for some time without learning about Acegi. Now I have a good excuse to fill in that gap.

    Thanks!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Stuff I've learned recently...

Subscribe now to keep reading and get access to the full archive.

Continue reading