360 Degrees of Form Processing with Grails and GrailsUI Modal Dialogs

Daniel Honig is an independent architect/developer/consultant living in New York City. Daniel is a contributor to GrailsUI and is writing this post as a guest for the benefit of GrailsUI users.

So recently I was reviewing some work that was done for us by a few developers of our team and discovered that we had some very ugly code that used an iframe inside of a gui dialog for a form submission. This resulted in the dialog appearing with scroll bars and other undesirable behavior.

Why was it that they had chosen to use an iframe instead of  just  using a div and pure ajax?  My assumption is that they did not have a good understanding of form processing using ajax inside of a dialog. Regardless of not having this experience myself,  I was determined to get this functionality cleaned up and  I’m happy to report that the nice clean integration between GrailsUI and YUI made this a much more pleasurable task than using YUI directly.   It seemed to me that this would be a fairly common need.  So I wanted to share my experiences and show how I was able to use Grails, GrailsUI and YUI to deliver this functionality without using the ugly iframe solution.   It really is quite simple with Grails and GrailsUI.   GrailsUI will take care of the 80%, but the last 20% has to be slugged out with our own Javascript glue-code.

So let’s get started:

Goal:

Post a form from within a dialog.   The form submission must inform the user of any errors and provide visual feedback of any modified state.  We will address create/add scenarios as well as edit scenarios.

Process:

Below is a quick sketch of the steps in form processing when using a modal dialog:

ajax-form-submission-diagram1

Rendering and Setting up the dialog

The page you wish to use the dialog on must contain a gui:resources tag with components=['dialog'], the gui:dialog tag and the JavaScript code that is needed to glue everything together.

We declare the dialog in our page as:


<gui:dialog
form="true"
id="locationDialog"
width="570px"
title="Create/Edit Location"
draggable="true"
action="remoteSaveOrEditLocation"
update="locationForm"
modal="true">
<div id="locationForm"></div>
</gui:dialog>

Let’s go over the most important options:

  • id= we set locationForm so that we can access this dialog later as GRAILSUI.locationForm
  • action=”  this is the grails controller action that will respond to the asynchronous form submission of the add/edit scenario.
  • update=” This is the div that should be updated on form submission.   We specify this as the div called ‘locationForm’ as the content of the dialog.
  • form=’true’ This tells GrailsUI that the dialog is a form and GrailsUI will wrap the content of the dialog in a form tag (among other things).

This takes care of rendering the dialog but, as we have not used the ‘triggers’ argument, we need to give it a way to be shown for both the add and edit scenario.

We’ll do this with a combination of Javascript, GSP and HTML.

Opening the dialog for a create scenario

First we’ll create an anchor to act as a ‘button’ for the user to open the dialog.


<a href="javascript:void(0)" id="openForNew">Create New Location</a>

Now we need to add some JavaScript to manage the dialog, the following code will listen for the click event of  the anchor with id: ‘openForNew’

When clicked, we then call a JavaScript function to populate the dialog with the response from a grails controller action which renders the location form.  We’ll use the grails createLink tag inside our custom JavaScript to ensure our code stays working across environments and contexts our code needs to work in.

GrailsUI component accessibility means that we can access our custom dialog as GRAILSUI.locationDialog anywhere we need in our own custom JavaScript.  So here is the code that we need to register the dialog with the click event of the anchor.  Upon the anchor being clicked it will invoke the openForNew function, which calls populate dialog to fire an ajax call and place the response inside the dialog.  We’ll put this code inside an onDomReady block and altogether it looks like this:


YAHOO.util.Event.onDOMReady(function() {
YAHOO.util.Event.addListener("openForNew", "click", openForNew, GRAILSUI.locationDialog, true);
function openForNew(e) {
populateDialog('${createLink(controller:'location',action:'presentLocationForm')}');
}

var populateDialog = function(url) {
var callback = {
success: function(o) {
GRAILSUI.util.replaceWithServerResponse(document.getElementById('locationForm'), o);
GRAILSUI.locationDialog.show();
},
function(o) {}
};
YAHOO.util.Connect.asyncRequest('POST', url, callback);
};
};

When the openForNew link is clicked, the listener fires the openForNew function which fires an asynchronous call to retrieve the dialog content and place it in the dialog via the utility function in GrailsUI: GRAILSUI.util.replaceWithServerResponse.

Opening the dialog for an edit scenario

Opening the dialog for an edit scenario is only slightly more complicated.  In my page, the edit link will appear in a table where each entity is one row of the table.   We need to extend the logic for opening the dialog to discern which entity or row in the table the user is attempting to edit.

To identify which one the user is editing we’ll append the id of the domain object to the row they are editing, inside of our <g:each tag we would write the markup to create the link


<a href="javascript:void(0)" id="openForEdit_${location.id}"><font color="#330066"><b><u>Edit</u></b></font></a>

To register the the link with the listener we add the following gsp code inside the onDomReady block of our custom script:


<g:each in="${locationList}"  var="location">
YAHOO.util.Event.addListener("openForEdit_${location.id}", "click", openForEdit, GRAILSUI.locationDialog, true);
</g:each>

Inside our GSP page and inside the JavaScript onDomReady function we add the following code:


function openForEdit(e) {
var target = e.target.parentNode.parentNode.parentNode //This is cruft that refelects the structure of the link inside the enclosing link
var locationId = target.id.split('_')[1] //After we have the link from the cruft, examine the id for the id of the location the user is editing.

populateDialog('${createLink(controller:'location',action:'presentLocationForm')}' + '/' + locationId);
}

Controller code to present the form inside the dialog

I’m using a command object as a way around some issues with my domain classes.  If an ID is passed in, we know it is an edit scenario and copy the values needed to the command object and return the form to the user, otherwise we assume it is a create scenario. As a result of this copying, we end up violating DRY a little bit, but for the moment it was a required compromise.


def presentLocationForm = {
def cmd = new ModifyLocationCommand()
if (params.id) {
def location = Location.get(params.id)
cmd.id = params.id
cmd.title = location.name
cmd.address1 = location.address.address1
cmd.address2 = location.address.address2
cmd.state = location.address.state
cmd.city = location.address.city
cmd.postalCode = location.address.postalCode
cmd.number = location.phones.iterator().toList()[0].number
cmd.email = location.email
cmd.website = location.website
}
render(template: 'activityLocationsPopUp', model: [cmd: cmd])
}

GSP Code for the dialog content

The view rendered by the controller action is defined in a file called ‘_activityLocationsPopup.gsp’
The abbreviated code for the view is:


<div id="locationForm">
<div>
<div class="message">${flash.message}</div>
</div>
<div>
<div class="dialog">
<table border="0">
<tbody>
<tr class="prop">
<td class="name" valign="top">
<label for="title">Title:</label>
</td>
<td class="value ${hasErrors(bean: cmd, field: 'title', 'errors')}" valign="top">
<input id="title" name="title" value="${fieldValue(bean: cmd, field: 'title')}"/>
</td>
</tr>
<tr>
<td class="name" valign="top">
<label for="address1">Address1:</label>
</td>
<td class="value ${hasErrors(bean: cmd, field: 'address1', 'errors')}" valign="top">
<input id="address1" name="address1" type="text"
value="${fieldValue(bean: cmd, field: 'address1')}"/>
</td>
</tr>
<tr class="prop">
<td class="name" valign="top">
<label for="address2">Address2:</label>
</td>
<td class="value ${hasErrors(bean: cmd, field: 'address2', 'errors')}" valign="top">
<input id="address2" name="address2" type="text"
value="${fieldValue(bean: cmd, field: 'address2')}"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

The important thing to note here is that the code enclosing the form is enclosed in the div with the id ‘locationForm’.   This is key so the code that does the Ajax replacement on form submission can find the div it needs to replace when the form is submitted.

Form Submission and Error Handling

So now we have the form being presented inside the dialog with submit and cancel buttons being generated by the GrailsUI as a result of us specifying form=’true’ in the creation of the dialog.   For simplicity I decided to handle the add and edit scenario with the same controller action as I did when presenting the form.

The action that responds to form submission looks like this:


def remoteSaveOrEditLocation = {ModifyLocationCommand cmd ->
if (cmd.hasErrors()) {
flash.errors = cmd.errors
//This is required to signal failure during the AJAX async communication
response.status = response.SC_INTERNAL_SERVER_ERROR
render(template: 'activityLocationsPopUp', model: [cmd: cmd])
} else {
if (cmd.id == null||cmd.id=='') {
Partner partner = PartnerAccount.get(session.account.id).partner
def newLocation= new Location()
newLocation.name=cmd.title
newLocation.address=new Address(address1:cmd.address1,address2:cmd.address2,state:cmd.state,city:cmd.city,postalCode:cmd.postalCode)
newLocation.addToPhones(new Phone(number:cmd.number, description:'Primay Contact', type:'work'))
locationService.saveNewLocation(partner, newLocation)
log.debug("The new location was  saved")
} else {
Partner partner = PartnerAccount.get(session.account.id).partner
locationService.updateLocationForPartnerAndCommand(partner, cmd)
log.debug("The new location was updated")
}
render('The location $cmd.location.name has been saved.')
}
}

Handling Success and Failure

We need to add some code to handle the success and failure of the form submission.   On failure or validation errors we want to be able to display the dialog with all of the validation errors.   On success we will provide some sort of visual feedback to the user that the update has occurred.

In the code below the most important line is:

GRAILSUI.locationDialog.callback = {success:doWhenSuccess,failure:doWhenFailed}

This changes the callback of the dialog and assigns it our own custom callbacks.

The code below will place the dialog with the error messages as rendered by the gsp template and will trigger full page refresh on success.   The full page refresh is just a short cut I had to take for the moment.   I will go back later and do some dom manipulation that avoids having to execute the full page refresh.


var doWhenSuccess = function(o) {
var replaceDiv = document.getElementById("locationForm");
GRAILSUI.util.replaceWithServerResponse(replaceDiv, o);
GRAILSUI.locationDialog.hide();

//ugly oldschool full page reload
window.location.reload();
};

var doWhenFailed = function(o) {
GRAILSUI.locationDialog.show()
var replaceDiv = document.getElementById("locationForm");
GRAILSUI.util.replaceWithServerResponse(replaceDiv, o);
GRAILSUI.locationDialog.show()
};

GRAILSUI.locationDialog.callback = {success:doWhenSuccess,failure:doWhenFailed};

Issues:

  • Used a command object as a way of skirting some validation problems in my domain.  This required me to violate DRY.
  • When obtaining the id of the domain object I am trying to edit e.target.parentNode.parentNode.parentNode  is a poor way of retrieving the event target.  Perhaps someone can suggest a better way of accessing the node that contains the id?
  • Issuing a full page refresh on success is far from optimal but I’ll replace it later by having my controller render a div and doing some dom manipulation
This entry was posted in grails plugins, javascript and tagged , , , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

9 Comments

  1. Rollo Tomazzi
    Posted March 28, 2009 at 9:12 am | Permalink

    Great post.
    There’s a lot to digest in here, but that’s definitely worth it. A great time saver too.
    Thanks a lot for this.
    Rollo

  2. Luis
    Posted May 15, 2009 at 9:27 am | Permalink

    Hi,

    Interesting post.

    I have set out and tried to use your recipe, i’ve been trying to fix it for 2 days, but i am struggling. I think it does not work even after removing your command objects and porting the code to a simple domain class.

    The first item i had to change is this:

    in populateDialog, the callback should be a map with two items, a succcess entry and a failure entry. The second entry, misses the name of the key:
    function(o) {}
    should be:
    failure: function(o) {}

    Then, i finally dialog showing up and being constructed from the popup template, but when I click on submit the form does nothing, it does not close, and it does not contact the server (at all, no error in url on my part here).

    I am convinced that there is a crucial part of the recipe that you have forgotten to mention, and it has something to do with binding the submit button to the form.

    Any help would be greatly appreciated. There is too much i do not understand yet in grailsUI and YUI to be able to diagnose this on my own. Otherwise i’ll have to drop this and do somthing more easily controllable based on jQuery.

    Just in case, i don’t want to sound over the board critical, I do appreciate the time you spent writing this down, but the examples are not usable as they are written down, and i can’t fix them.

  3. Gregor
    Posted June 29, 2009 at 9:55 am | Permalink

    Hey Luis,

    did you ever get behind that sicking-point? This seems to be a bug in grailsui and I’m not getting past it…

    Gregor

  4. Jeff
    Posted June 29, 2009 at 10:28 pm | Permalink

    Am wondering how you active the custom callback. I am having trouble getting the custom callback to be recognized. gui keeps creating the generic ones. I removed the update attribute and that had no difference.

    thanks
    jeff

  5. Posted February 19, 2010 at 2:47 pm | Permalink

    id= we set locationForm so that we can access this dialog later as GRAILSUI.locationForm
    Don’t you mean locationDialog? locationForm is the part of the page you are updating, right?

  6. Posted February 19, 2010 at 3:32 pm | Permalink

    Why did you need to do the window.location.reload()?
    You’ve also got a type in the open for edit comment “refelects”

  7. Posted February 19, 2010 at 4:24 pm | Permalink

    Have been looking at an example Dan Wellman did of a Dialog in the Packt YUi book and he always created a namespace to begin with before adding components.
    eg
    YAHOO.namespace(”grailsUI.dialog”);
    Then current code for line like:
    function init_dlg_LocationDialog() {
    becomes:
    YAHOO.grailsUI.dialog.init_dlg_LocationDialog = function () {
    and you use the fully qualified namespace in the onDOMReady Event method…
    Is there any reason why this approach wasn’t taken with the plug-in?

  8. Posted February 19, 2010 at 5:17 pm | Permalink

    Line 13 of the third listing with function(o)… Should this be prefaced by failure: ???

  9. Shane
    Posted February 25, 2010 at 6:05 pm | Permalink

    Has anyone got this technique working? It all seems to make sense, but I can’t get the error handling to work correctly

2 Trackbacks

  1. [...] Taylor and Daniel Honig were able to provide these links which helped me a bit with my quest 1 [...]

  2. By Blog bookmarks 09/05/2010 « My Diigo bookmarks on September 4, 2010 at 11:30 pm

    [...] 360 Degrees of Form Processing with Grails and GrailsUI Modal Dialogs [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*
Check out the latest GroovyMag to see an interview with me about the 1.1 release of GrailsUI: