Wednesday, 2 March 2011

Custom Sitemesh in Grails

So we've been using Grails for an internal application and it comes with Sitemesh out of the box which is great ... BUT as soon as you want to tailor how you use Sitemesh then you will run into difficulties. Here is an example ...

I wanted to use Sitemesh's powerful pattern based decorator mapping, which would allow me to map different layouts/ decorators to pages based on URL patterns. I'd used this in a previous life and found it very, very good and to be honest, one of the main reasons I would want to actually use Sitemesh.

<decorators defaultdir="/layouts">
    <decorator name="default" page="default/main.gsp">
          <pattern>/*</pattern>
    </decorator>
     <decorator name="other" page="other/main.gsp">
          <pattern>/other/*</pattern>
    </decorator>
</decorators>

but Grails uses its own GrailsLayoutDecoratorMapper in the sitemesh.xml file

<mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />

"No problem!", I thought. One swift google later I found that I could happily chain the decorator mappers. I chained, and extracted my decorators to a decorators.xml file for good measure and the resulting code in the sitemesh.xml file is below ...

<sitemesh>
    <property name="decorators-file" value="/WEB-INF/decorators.xml" />
    ......
    ......
    <decorator-mappers>
        <mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
            <param name="config" value="${decorators-file}" />
        </mapper>
        <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
    </decorator-mappers>
</sitemesh>

If this works for you great! It worked for my local environment but as soon as I deployed to test environment it blew up. Any Grails enthusiast can probably guess why. The sitemesh.xml file and decorators.xml file live at the root level of WEB-INF and in particular the sitemesh.xml is generated by Grails which will overwrite any changes you make manually. To get around this .... and this is the good bit ..... cue fanfare and much trumpeting ... you need to tell Grails to exclude the sitemesh.xml from the list of files it generates and use a custom-sitemesh.xml instead. You should manually change the custom-sitemesh file moving forwards as this is the file that your applicatiion will use.

To do this you need to edit your grails.war.resources property in your BuildConfig.groovy file as follows

grails.war.resources = { stagingDir, args ->
copy(file: "grails-app/conf/custom-sitemesh.xml", tofile: "${stagingDir}/WEB-INF/sitemesh.xml", verbose: true, overwrite: true)
}