Tech Note: Grails Custom One-To-Many Scaffolding

{ Dan Stieglitz // Groovy/Grails // April 10, 2008 }

One small issue we have with Grails is that out-of-the-box it doesn't scaffold a UI to deal with one-to-many (OTM) relationships for existing objects. You can add a new instance of an entity to a container object when editing it, but if you have pre-existing objects you can't add them to the container. When you're in the early phases of an application and are tweaking your domain model this can be a big help. Here I'm going to present a quick way of modifying the scaffolding templates to allow you to generate a UI that can allow this behavior. There's a lot of value in knowing how to modify the Grails default behavior and this short tutorial should give you a little introduction into how Grails works under the covers.

Create the application

grails create-app OneToManyScaffolding

Create the domain model

In this example we create a Container and an Component object, where Container contains many Components.
grails create-domain-class Container
grails create-domain-class Component
Next, flesh out the domain objects:
class Component {
	
	String name
	
        String toString() {
		return name
	}
}
We implement the toString() method so that when the scaffold displays the objects in the UI, we'll get a value that has some meaning. We also build our Container domain class having a hasMany reference to our newly-created component class.
class Container {
	
        String name
	static hasMany = [ components : Component ]

        String toString() {
                return name
        }
}
Let's run the app to see what we get out-of-the-box. First we'll create a container using the UI (ContainerController -> New Container). When we attempt to edit our new container we get an edit screen with an "Add Component" link; however, clicking this link takes us to the "Create Component" screen. If we'd had some pre-created Components we wanted to add to this container the out-of-the-box UI would not allows us to do this. We can implement this behavior for the default UI by modifying the templates that generate these pages.

Install the scaffolding templates

First we'll need to install the templates themselves, and Grails makes this quite easy. You'll only need to issue:
grails install-templates
When this operation completes there will be a src/templates/scaffolding directory added to the application. This directory contains all of the Groovy templates that are used to generate the scaffolded pages when you issue a "generate-all ..." command. There is also a GSP that generates the "editor" for domain objects called "renderEditor.template" that contains logic we'll modify to get this working.

Modify the scaffolding templates

Open the renderEditor.template file and find the "renderOneToMany" method. The last line before the method returns generates the link we see that allows us to add (currently only new) objects to our container object. Add the following line below:
pw.println "
Associate ${property.referencedDomainClass.shortName}";
This will create an "Associate ..." link below the "Add ..." link on the Container edit page. Clicking on the link will call the "list" action of the component class and pass in 3 paramters: component_id, source, and callback. To complete the flow for associating the objects we'll need to create a new method, "choose," in the template that generates the controllers. This template is in the same scaffolding directory and is called Controller.groovy. Add the method definition to the end of the class:
    def choose = {
        redirect(controller:params.source,action:params.callback,params:params)
    }
We'll need to regenerate the controllers now since we've changed the scaffolding templates, so issue a "grails generate-all Container" and "grails generate-all Component."

Now we have to crack open the list template and modify it to produce links that know when we're trying to list instances and when we're trying to select an association. Each instance link is generated in an each closure near the bottom on the template. We'll add some code to generate a different link when a "callback" parameter is passed in, indicating we've come from an "Associate ..." link and not an "Add ..." link.

        
            \${${propertyName}.${p.name}?.encodeAsHTML()}
        
        
            \${${propertyName}.${p.name}?.encodeAsHTML()}
        
We've modified the behavior to call the "choose" method we implemented above on the Container class, which will in turn call the method specified in the "callback" parameter we've been passing around in the URLs. The callback method will be called on the "source" object also passed around. In this specific case, Grails expects to see an addComponent method in the Container controller, so let's go ahead and add that method:
    def addComponent = {
    	def container = Container.get(params["container_id"])
    	def component = Component.get(params["id"])
    	container.addToComponents(component)
    	render(view:'edit',model:[container:container])
    }
That completes the code for the flow, so now you should be able to add components to your model, and then associate them with a controller in your view. Be careful not to overwrite your custom controller methods when you regenerate controllers, or better yet, tweak this code so those methods can live in a service.

Comments 11 comments


Sorry I don’t quite follow the last 2 steps. I s ther something missing in the list template code?

Posted by  on  04/21  at  04:47 AM

I think you’re referring to the problem with the code fragment display, which we had not noticed. If you read the article from the main blog page it’s more legible (http://www.stainlesscode.com/blog-articles/). I’ll get our web team to work on this now.

Posted by  on  04/21  at  10:48 AM

Thanks for the post, it was very helpful.

In your renderEditor.template code, there seems to be extra “ (double quotes) added in front of the \” which makes it fail when copied and pasted.

I wonder if it would be possible to make the choose into checkmarks and multiple select items into a list.

Posted by  on  04/28  at  10:44 AM

I had to change the code for the renderEditor.template to the following:

pw.println “
<g:link controller=\"${property.referencedDomainClass.propertyName}\" params=\"[\"${domainClass.propertyName}_id\”:${domainClass.propertyName}?.id,\"source\”:\"${domainClass.propertyName}\”,\"callback\”:\"add${property.referencedDomainClass.shortName}\"]\" action=\"list\">Associate ${property.referencedDomainClass.shortName}</g:link>”

The basic problems were that there were too many quotes and existing quotes were not escaped.

Posted by  on  06/03  at  10:11 AM

Do you happen to have instructions for modifying this to work with dynamic scaffolding? I’d like to change the last “addComponent” method to be dynamic where I don’t have to do a generate-all.

Posted by Matt Raible  on  07/21  at  12:08 AM

As for the renderEditor.template association link, here’s what I got working.  I removed all quotes around the three name/value pairs within the params attribute.

pw.println “<br><g:link controller=\"${property.referencedDomainClass.propertyName}\" params=\"[${domainClass.propertyName}_id:${domainClass.propertyName}?.id,source:’${domainClass.propertyName}’,callback:’add${property.referencedDomainClass.shortName}’]\" action=\"list\">Associate ${property.referencedDomainClass.shortName}</g:link>”;

Posted by  on  08/01  at  11:51 AM

Aren’t Groovy’s strings (i.e. triple quotes) better for this?

Posted by dion gillard  on  08/06  at  03:17 AM

Thanks to all who have contributed to correcting errors in the code above.

As far as dynamic scaffolding goes, I’m not sure how best to implement a solution that would work for different classes at run time. The main problem here being the addToXXX method, which is the recommended method of adding to a collection. That being said, some options might be:

- send extra parameters into the flow indicating the class names involved and literally do a ‘this.classLoader.loadClass(),’ although this is more of a hack than an elegant solution. It doesn’t solve the addToXXX problem either.

- maybe using Groovy mixins to achieve this, and having the controller implement an ‘OTMAware’ interface. Here you could query the domain classes’ collections and inject an addOtmXXX method that builds the closure at runtime.

The latter would be an interesting exercise, if anyone attempts it let me know.

Posted by Dan Stieglitz  on  08/13  at  07:59 AM

Hey all:

I’m not sure if this is quite the place to put this, but I’ve gone a similar route, but have dynamic controller code that will deal with (I think) any many to many or one to many relationship from the view files.  I ended up doing something similar to what you’ve got for the view files, but also have dynamic controller code.

Can someone have a look here: http://michaelkimsal.com/blog/grails-scaffolding-improvement/ or directly at the code here: http://code.google.com/p/kimsal-misc/source/browse/#svn/trunk/grails/scaffold and see if it’s worth perhaps combining these approaches in to one unified method?  I think I’m on the right track, but need some feedback as to whether I’m missing anything obvious (or doing something patently wrong!)

Thanks!

Posted by Michael Kimsal  on  08/29  at  06:39 PM

Really very helpful article. Thanks for your details help. Very much informative. Thanks for sharing with us.

Posted by eve isk  on  09/16  at  12:14 PM

Something that I noticed is a little of lack of performance, I think that it is caused by the use of reflection to get the metadata from the model. Anyway, it is a very interesting and nice framework that worth the try.

Posted by team building consultants  on  10/01  at  04:24 AM

Add your own comment below.

Name:

Email:

Location:

URL:

Remember my personal information

Notify me of follow-up comments?

Submit the word you see below:


<< Back to main