In a Grails application we use Groovy Server Pages (GSP) views and templates to create content we want to display in the web browser. Since Grails 2 we can use the groovyPageRenderer
instance to render GSP views and templates outside a controller. For example we can render content in a service or other classes outside the scope of a web request.
The groovyPageRenderer
instance is of type grails.gsp.PageRenderer
and has a render()
method like we use in a controller. We can pass a view or template name together with a model of data to be used in the GSP. The result is a String
value.
The class also contains a renderTo()
method that can be used to generate output to a Writer
object. For example we can generate files with the output of the GSP rendering by passing a FileWriter
instance to this method.
In the following sample we create a GSP view confirm.gsp and a GSP template _welcome.gsp. We also create a Grails service with two methods to use the GSP view and template.
%{-- File: grails-app/views/email/_welcome.gsp --}% Hi, ${username}
%{-- File: grails-app/views/email/confirm.gsp --}% <!doctype html> <head> <title>Confirmation</title> </head> <body> <h2><g:render template="/email/welcome" model="[username: username]"/></h2> <p> Thank you for your registration. </p> </body> </html>
// File: grails-app/services/page/renderer/RenderService.groovy package page.renderer import grails.gsp.PageRenderer class RenderService { /** * Use the variable name groovyPageRenderer to enable * automatic Spring bean binding. Notice the variable * starts with groovy..., could be cause of confusing because * the type is PageRenderer without prefix Groovy.... */ PageRenderer groovyPageRenderer String createConfirmMessage() { groovyPageRenderer.render view: '/email/confirm', model: [username: findUsername()] } String createWelcomeMessage() { groovyPageRenderer.render template: '/email/welcome', model: [username: findUsername()] } private String findUsername() { // Lookup username, for this example we return a // simple String value. 'mrhaki' } }
Next we create a integration test to see if our content is rendered correctly:
// File: test/integration/page/renderer/RenderOutputTests.groovy package page.renderer import org.junit.Assert import org.junit.Test class RenderOutputTests { RenderService renderService @Test void welcomeMessage() { Assert.assertEquals 'Hi, mrhaki', renderService.createWelcomeMessage().trim() } @Test void confirmMessage() { final String expectedOutput = ''' <!doctype html> <head> <title>Confirmation</title> </head> <body> <h2> Hi, mrhaki</h2> <p> Thank you for your registration. </p> </body> </html>''' Assert.assertEquals expectedOutput.stripIndent(), renderService.createConfirmMessage() } }
We can run our test and everything is okay:
$ grails test-app RenderOutputTests | Completed 2 integration tests, 0 failed in 1051ms | Tests PASSED - view reports in target/test-reports
We can use tags from tag libraries in our GSP views and templates. The Sitemesh layouts cannot be used. The PageRenderer
works outside of the request scope, which is necessary for the Sitemesh layouts. Because we are outside of the web request scope we cannot generate absolute links in the GSP view directly. If we change the confirm GSP view and add a tag to create an absolute link we get an UnsupportedOperationExeption
with the message You cannot read the server port in non-request rendering operations.
%{-- File: grails-app/views/email/confirm.gsp --}% <!doctype html> <head> <title>Confirmation</title> </head> <body> <h2><g:render template="/email/welcome" model="[username: username]"/></h2> <p> Thank you for your registration. </p> <p> To use the application can you directly go to the <g:link absolute="true" controller="index">home page</g:link>. </p> </body> </html>
But we can simply workaround this issue. Remember that since Grails 2 we can use the grailsLinkGenerator
to generate links in for example a service. So we create our absolute link with the grailsLinkGenerator
and pass it as a model attribute to our GPS view.
package page.renderer import grails.gsp.PageRenderer import org.codehaus.groovy.grails.web.mapping.LinkGenerator class RenderService { PageRenderer groovyPageRenderer LinkGenerator grailsLinkGenerator String createConfirmMessage() { final String homePageLink = grailsLinkGenerator.link(controller: 'index', absolute: true) groovyPageRenderer.render(view: '/email/confirm', model: [link: homePageLink, username: findUsername()]) } String createWelcomeMessage() { groovyPageRenderer.render template: '/email/welcome', model: [username: findUsername()] } private String findUsername() { // Lookup username, for this example we return a // simple String value. 'mrhaki' } }
%{-- File: grails-app/views/email/confirm.gsp --}% <!doctype html> <head> <title>Confirmation</title> </head> <body> <h2><g:render template="/email/welcome" model="[username: username]"/></h2> <p> Thank you for your registration. </p> <p> To use the application can you directly go to the <a href="${link}">home page</a>. </p> </body> </html>