Skip to content


Grails one-to-many dynamic forms

After searching around trying to find a good way to implement one-to-many dynamic forms in Grails, I have finally come across this post which does a very good job at explaining the details.

What I was basically looking for is a clean way to implement saving my domain objects in the backend, rather than the hacked way I did by hand picking request parameters and manually setting up my domain objects (I’m still fairly new to Grails), and the official docs fail to shed the light on the subtle details I found in this post, which is why I decided to post my own version of the one-to-many dynamic forms but with a little bit more complex domain objects to illustrate the use of enums which also I found is a bit of a gray area in the docs (or at least maybe for me).
Make sure you head over and read the original post, as I will not go through all the details already mentioned over there. This example was developed using Grails version 1.3.3

The source code for everything I will be talking about can be grabbed as a .zip here


Update (28 Aug 2010): As Brad pointed out, removing a new phone that was still not persisted did not really remove the phone, this is because the dom elements were hidden instead of removed from the phone which caused the values to be submitted anyways. I’ve updated the source code in the post to fix that by adding a ‘new’ flag when it is still ‘true’ the form elements should be completely removed, while when it is false, they should stay since we want the deleted flag to be submitted.

Update (19 Oct 2010): Updated the source code to incorportate the fixes from the previous update as well as some other bugs. Make sure you grab this updated sources for a better reference. Also fixed a typo in the post as pointed out by Brad.

Update (11 Feb 2011): Added a column name mapping for the Phone domain class which caused problems creating the table in MySQL since the column name “index” is a reserved word


I will be building a simple ‘Phone Book’ application that allows you to create Contacts were each contact can have multiple phone numbers. I will start by defining the domain classes. First up, our Contact

package blog.omarello

import org.apache.commons.collections.list.LazyList;
import org.apache.commons.collections.FactoryUtils;

class Contact {
    static constraints = {
        firstName(blank:false)
        lastName(blank:false)
    }
    String firstName
    String lastName
    String nickName
    List phones = new ArrayList()
    static hasMany = [ phones:Phone ]

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

    def getPhonesList() {
        return LazyList.decorate(
              phones,
              FactoryUtils.instantiateFactory(Phone.class))
    }

    def String toString() {
        return "${lastName}, ${firstName}"
    }
}

The important thing to note here is the LazyList and the cascade mapping. according to the above our contact will be able to store a list of phones of type Phone. Our next domain class is the Phone class which looks like this.

package blog.omarello

class Phone {
    public enum PhoneType{
        H("Home"),
        M("Mobile"),
        W("Work")

        final String value;

        PhoneType(String value) {
            this.value = value;
        }
        String toString(){
            value;
        }
        String getKey(){
            name()
        }
        static list() {
            [H, M, W]
        }
    }

    static constraints = {
        index(blank:false, min:0)
        number(blank:false)
        type(blank:false, inList:PhoneType.list(), minSize:1, maxSize:1)
    }

    static mapping = {
        index column:"phone_index"
    }

    int index
    String number
    PhoneType type
    boolean deleted
    static transients = [ 'deleted' ]
    static belongsTo = [ contact:Contact ]

    def String toString() {
        return "($index) ${number} - ${type.value}"
    }
}

I have added an enum here to show how you can persist enums and use them in your constraints (there might be better ways to do this I am open to suggestions). The enum will define the phone types that I can use for each phone number, and will be rendered as a select list on the page. The index integer is just to keep track of the position of each phone number in the list (I might come back to this in a later post), the rest is quite similar to the original post. The deleted boolean will be used to keep track of which objects were deleted in the view so that they can be later deleted from the database which is why is defined in the transients list (since we don’t want to persist this property)

Now that we have defined that a contact can have many phone numbers and a phone number belongs to contact, we can go ahead and start writing our views. You can stub out a controller and the corresponding views using grails generate-all blog.omarello.Contact and then edit them accordingly.

What we will do now, is create three template .gsp files which we will use to keep things clean and avoid repetition in multiple files:

  • _contact.gsp : This will be used to render the shared input fields within the create.gsp and edit.gsp file. Keeps things cleaner and avoids repetition
  • _phones.gsp: This will contain out main javascript logic (using jquery) and a loop to render each phone entry
  • _phone.gsp : This will represent an individual phone entry

_contact.gsp will basically contain the view contents that were stubbed out by the generate-all command, make sure to check the source code for the full picture, but the most important part is rendering the _phones.gsp template in place of the default list we get from ‘generate all’, notice how we pass our contact instance object to the template.

....
....
<tr class="prop">
   <td valign="top" class="name">
      <label for="phones"><g:message code="contact.phones.label" default="Phones List" /></label>
   </td>
   <td valign="top" class="value ${hasErrors(bean: contactInstance, field: 'phones', 'errors')}">
       <!-- Render the phones template (_phones.gsp) here -->
       <g:render template="phones" model="['contactInstance':contactInstance]" />
       <!-- Render the phones template (_phones.gsp) here -->
   </td>
</tr>
....
....

Note that when rendering templates you do not have to include the underscore that is in the file name.

Now our dynamic javascript behavior is contained in the _phones.gsp file (javascript can be included in an external .js file if you want to) which looks something like this.

<script type="text/javascript">
    var childCount = ${contactInstance?.phones.size()} + 0;

    function addPhone(){
      var clone = $("#phone_clone").clone()
      var htmlId = 'phonesList['+childCount+'].';
      var phoneInput = clone.find("input[id$=number]");

      clone.find("input[id$=id]")
             .attr('id',htmlId + 'id')
             .attr('name',htmlId + 'id');
      clone.find("input[id$=deleted]")
              .attr('id',htmlId + 'deleted')
              .attr('name',htmlId + 'deleted');
      clone.find("input[id$=new]")
              .attr('id',htmlId + 'new')
              .attr('name',htmlId + 'new')
              .attr('value', 'true');
      phoneInput.attr('id',htmlId + 'number')
              .attr('name',htmlId + 'number');
      clone.find("select[id$=type]")
              .attr('id',htmlId + 'type')
              .attr('name',htmlId + 'type');

      clone.attr('id', 'phone'+childCount);
      $("#childList").append(clone);
      clone.show();
      phoneInput.focus();
      childCount++;
    }

    //bind click event on delete buttons using jquery live
    $('.del-phone').live('click', function() {
        //find the parent div
        var prnt = $(this).parents(".phone-div");
        //find the deleted hidden input
        var delInput = prnt.find("input[id$=deleted]");
        //check if this is still not persisted
        var newValue = prnt.find("input[id$=new]").attr('value');
        //if it is new then i can safely remove from dom
        if(newValue == 'true'){
            prnt.remove();
        }else{
            //set the deletedFlag to true
            delInput.attr('value','true');
            //hide the div
            prnt.hide();
        }        
    });

</script>

<div id="childList">
    <g:each var="phone" in="${contactInstance.phones}" status="i">
        <!-- Render the phone template (_phone.gsp) here -->
        <g:render template='phone' model="['phone':phone,'i':i,'hidden':false]"/>
        <!-- Render the phone template (_phone.gsp) here -->
    </g:each>
</div>
<input type="button" value="Add Phone" onclick="addPhone();" />

I have used a different approach than the original post here, I am basically relying on cloning an existing dom object rather than creating the html by hand since this will get a bit messy when you have some non-trivial form. Using jquery selectors to update my input ids in the addPhone() function (e.g. The selector input[id$=deleted] will find all inputs with an id attribute that ends with deleted). Also I am using jQuery’s live() to bind click events to my remove button, I find this a little bit cleaner.
I will come back to the dom object that will be cloned in a second, but the hidden:’false’ part in the render is related to the clone.

The above template loops over all the phones that a contact has, and renders them using this template (_phone.gsp)

<div id="phone${i}" class="phone-div" <g:if test="${hidden}">style="display:none;"</g:if>>
    <g:hiddenField name='phonesList[${i}].id' value='${phone?.id}'/>
    <g:hiddenField name='phonesList[${i}].deleted' value='false'/>
    <g:hiddenField name='phonesList[${i}].new' value='false'/>

    <g:textField name='phonesList[${i}].number' value='${phone?.number}' />
    <g:select name="phonesList[${i}].type"
        from="${blog.omarello.Phone.PhoneType.values()}"
        optionKey="key"
        optionValue="value"
        value = "${phone?.type?.key}"/>

    <span class="del-phone">
        <img src="${resource(dir:'images/skin', file:'icon_delete.png')}"
            style="vertical-align:middle;"/>
    </span>
</div>

The important thing to note here is the use of classes that are used for the jQuery selectors (class=”phone-div” for the main div and class=”del-phone” for the delete button), also the use of the g:select tag to render the enum list with the appropriate key/value.

Now we need to add the html part that will be cloned, and for that we will render the _phone.gsp template in the create.gsp and edit.gsp files (outside of the g:form tag) but we will set the hidden attribute to true so that this div is not visible to the user, but can be cloned by our javascript code like so

......
......
        </g:form>
    </div>
    <!-- Render the phone template (_phone.gsp) hidden so we can clone it -->
    <g:render template='phone' model="['phone':null,'i':'_clone','hidden':true]"/>
    <!-- Render the phone template (_phone.gsp) hidden so we can clone it -->
  </body>
</html>

Our views are now ready for prime time. The most important thing that we have accomplished here is that we are keeping track of the deleted flag on the hidden input which will be used in the controller to delete the phones from the database.

Now we need to make a little change to the controller so that we can remove the phones marked as deleted , plus I have added an extra loop to reset/update the index numbers of my phones (not relevant now, might talk about it later). Note that due to the naming convention of our form inputs, grails can automatically bind the request parameters and auto-magically create the domain class object for us. The controller’s update action should contain the following

......
......

contactInstance.properties = params

// find the phones that are marked for deletion
def _toBeDeleted = contactInstance.phones.findAll {(it?.deleted || (it == null))}

// if there are phones to be deleted remove them all
if (_toBeDeleted) {
    contactInstance.phones.removeAll(_toBeDeleted)
}

//update my indexes
contactInstance.phones.eachWithIndex(){phn, i ->
    phn.index = i
}
if (!contactInstance.hasErrors() && contactInstance.save(flush: true)) {
......
......

That’s it, we should be all set. Let give it a spin…. grails run-app and fireup your browser. here are some action shots.

Create a new contact

Create Contact

Contact Created

Update contact

Update Contact

List contacts

List Contacts

One thing I wanted to show was validation, but I could not make it work for the phone numbers, as you can see in this image, I am getting a validation error for only one of the empty phone numbers, and I did not know how to highlight the input box that was triggering this error, so if you have an ideas please don’t be shy to share them.

Validation

I hope someone finds this useful, as I have struggled to find resources that outline how to persist enums as well as how to deal with request parameters for one-to-many relationships without having to hack your way into hand crafting the domain classes. Off course kudos to the original poster for his comprehensive example.

Update (28 Aug 2010): As Brad pointed out, removing a new phone that was still not persisted did not really remove the phone, this is because the dom elements were hidden instead of removed from the phone which caused the values to be submitted anyways. I’ve updated the source code in the post to fix that by adding a ‘new’ flag when it is still ‘true’ the form elements should be completely removed, while when it is false, they should stay since we want the deleted flag to be submitted.

Posted in Programming.

Tagged with , , , , .


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.