An approach to processing dynamic one-to-many forms with Grails 2.1

An example of processing forms containing both parent and child objects (one-to-many relationship) in Grails 2..

Object Partners

I was recently faced with a (rather common) problem: process a form submission containing fields for a ‘parent’ and several ‘child’ records. For example, a form to create a Team record along with its many Player records. This is not a specialized problem, and certainly many developers have solved it one way or another, using the techniques available and applicable to their projects. I’m going to describe an approach to this problem for a web application using Grails 2.1 and a jQuery UI plugin, writetable.

Here is a brief description of the problem, with a simple example: We have two domain objects, Team and Player. There is a one-to-many association betweenTeam and Player. We want to allow users to create a Team, along with its Players, all in one form. Users should also be able to edit a Team, insert new Players, and edit or remove existing Players.

The Grails documentation does not provide much guidance to solving this problem, other than the brief section titled Data Binding and Many-ended Associations.  You may find, as I did, the following blog post helpful, although it seems to be a bit outdated for Grails 2.1 users: http://omarello.com/2010/08/grails-one-to-many-dynamic-forms/ . My approach is essentially a revision of omarello’s post, updated slightly for Grails 2.1. I’m also using a custom jQuery UI plugin (writetable) to generate the ‘child’ input elements, but that is not the main purpose of this article.

Our goal is to take advantage of Grails’ powerful data binding capabilities, while providing a simple and convenient form for our end-users and maintaining control over how Player objects might be removed. I’ll go over a very simple example to demonstrate the technique generally. Here is our Team class:

class Team {
    String name
    List player  

    static hasMany = [players : Player]  

    static mapping = {
      players cascade:"all-delete-orphan"
    }
}

And here is our Player class:

class Player {
    String firstName
    String lastName
    String position
    boolean deleted
static transients = [ 'deleted' ]
    static belongsTo = [team : Team]  

    static constraints = {
        firstName(blank: false)
        lastName(blank: false)
        position(blank: false)
    }
String toString() {
         "id=$id : ${firstName} $lastName, $position"
    }
}

The most important things to note here are the transient ‘deleted’ property of Player. As we’ll see, this plays a role in giving us control over how Players are removed from a Team. We define Team.players as a List so we can preserve the order of entries in the collection and access them with an index. Note that our design assumes that a Player is completely dependent on a Team, and can only belong to one Team. The “all-delete-orphan” mapping on Team.players ensures that a Player will be deleted when removed from a Team.  Also note a difference between our Team class and omarello’s Contact class: we do not have to add a special getter method to decorate the ‘players’ list with Apache Commons LazyList to grow the list automatically when an index value greater than the size of the list is used. Apparently Grails now does this for us implicitly.

Now let’s look at the controller code. To create a new Team, would could use a save() action as simple as:

def save() {
    def team = new Team(params)
    team.save()
    redirect(action: 'show', params: [id:team.id])
}

That was easy. We’re using the data binding features of Grails to automatically populate our Players list. There is a convention we must follow to take advantage of this, as the Grails documentation describes. We need to provide request parameters with the following naming convention: players[n].firstName, players[n].lastName, players[n].position, etc…, where ‘n’ is the 0-based position of a Player in the Team.players List.  The writetable jQuery UI plugin takes care of generating those HTML form inputs for us.

We don’t need any logic here regarding the removal of players because this is only to be used for the creation of new Teams. To edit existing Teams, we use the following update() action:

def update() {
    def team = Team.get(params.id)
    team.properties = params
    team.players.removeAll{ it.deleted }  

    team.save()
    redirect(action: 'show', params: [id:team.id])
}

This is where things are a bit more interesting. When an end-user removes a player from the team, we add a hidden input element named players[n].deleted and give it a value of ‘true’ (again, where ‘n’ is the index value of the row in the players list). Grails will update the removed Players accordingly. All we have to do is use the removeAll method to remove the Players that have been marked for deletion.

While I’m not crazy about ‘polluting’ the model with what I consider to be view/controller concerns (I’m referring to the ‘deleted’ property in Player to support the management of the Team.players list), the alternatives (as my limited imagination was able to recognize) were decidedly worse. For example, I could have implemented some Ajax solution, where an asynchronous call is issued when a user edits/deletes a Player. Actually that could be a good way handle it, although it’d be more ‘chatty’. Another idea was to break up the form into a wizard. Yuck. Anyway - the approach described here seemed to best fit the ”Do The Simplest Thing That Could Possibly Work” principle, overall. Thanks and credit to omarello for his earlier description of the technique.

The full example source can be found here: https://github.com/sflahave/writetable-example-with-grails

Share this Post

Related Blog Posts

JavaScript

Use JQuery Mobile’s Tools Suite to help you debug and improve your JQuery Mobile application

November 2nd, 2012

JQuery Mobile provides tools to help you gain insight and understanding into global configuration values, page events, and page change timings in your app.

Object Partners
JavaScript

Using CometDs hidden subscribeProps

September 27th, 2012

ConetD, a bayeux implementation, has a poorly documented feature that allows you to send information along with a subscription request: subscribeProps

Scott Bock
JavaScript

Embrace JavaScript Frameworks

September 12th, 2012

When developing web applications, its very common for a confusing blend of JavaScript and framework code to make its way into the code.

Object Partners

About the author