Loading...

April 13, 2011

Grails Goodness: Applying Layouts in Layouts Revisited

In the blog post Applying Layouts in Layouts we learned how to reuse layouts in a Grails application. Peter Ledbrook added a comment suggesting to use the <g:applyLayout/> in the Groovy Server Pages in the layouts directory instead of in the views. So this way we can use the default method of applying layouts in our views (using for example the <meta/> tag) and keep all <g:applyLayout/> tags in the layout pages. In this post we see how we can support this for our applications and still reuse the layouts like before.

Please read the previous blog post to see the layout structure of our sample Grails application. The main layout page is still grails-app/views/layouts/page.gsp and doesn't need to be changed:

<%-- File: grails-app/views/layouts/page.gsp --%>
<!DOCTYPE html>
<html>
<head>
    <title><g:layoutTitle default="Grails"/></title>
    <link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}"/>
    <link rel="stylesheet" href="${resource(dir: 'css', file: 'layout.css')}"/>
    <link rel="stylesheet" href="${resource(dir: 'css', file: 'fonts.css')}"/>
    <link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon"/>
    <g:layoutHead/>
</head>
<body>
<div id="header" class="clearfix">
    <div id="logo">
        <g:link uri="/"><g:message code="nav.home"/></g:link>
        <p><g:message code="title.website"/></p>
    </div>

    <div id="searchform">
        <g:form controller="search">
            <fieldset class="search">
                <input type="text" class="search-input" value="${message(code:'search.box.search')}" name="search" id="search-phrase" maxlength="100"/>
                <input type="submit" value="${message(code: 'search.box.submit')}" />
            </fieldset>
        </g:form>
    </div>

    <div id="navmenu">
        <ul>
            <li><g:link uri="/"><g:message code="nav.home"/></g:link></li>
            <li><g:link controller="product" action="list"><g:message code="nav.products"/></g:link></li>
        </ul>
    </div>
</div>

<g:layoutBody/>

<div id="footer">
    <p>Copyright © 2011 Hubert A. Klein Ikkink - <a href="http://www.mrhaki.com">mrhaki</a></p>
</div>
</body>
</html>

The layout for the homepage is defined by fiveblocks.gsp in the layouts directory. We change this file and use <g:applyLayout/> to apply the main page layout instead of using <meta name="layout" content="page"/>:

<%-- File: grails-app/views/layouts/fiveblocks.gsp --%>
<g:applyLayout name="page">
    <html>
    <head>
        <title><g:layoutTitle/></title>
        <g:layoutHead/>
    </head>
    <body>
    <div id="banner">
        <g:pageProperty name="page.banner"/>
    </div>

    <div id="left">
        <g:pageProperty name="page.left1"/>
        <g:pageProperty name="page.left2"/>
        <g:pageProperty name="page.left3"/>

        <div id="box-left">
            <g:pageProperty name="page.box-left"/>
        </div>

        <div id="box-right">
            <g:pageProperty name="page.box-right"/>
        </div>
    </div>

    <div id="right">
        <g:pageProperty name="page.right1"/>
        <g:pageProperty name="page.right2"/>
        <g:pageProperty name="page.right3"/>
    </div>
    </body>
    </html>
</g:applyLayout>

And now we use <meta name="layout" content="fiveblocks"/> in the homepage Groovy server page and define the content for our Sitemesh content blocks.

<%-- File: grails-app/views/templates/homepage.gsp --%>
<html>
<head>
    <meta name="layout" content="fiveblocks"/>
    <title><g:message code="title.homepage"/></title>
</head>
<body>
<content tag="banner">
    <h1>Welcome to Grails Layout Demo</h1>
</content>

<content tag="left1">
    <p>...</p>
</content>

<content tag="box-left">
    <p>...</p>
</content>

<content tag="box-right">
    <p>...</p>
</content>

<content tag="right1"> 
    <p>...</p> 
</content> 
</body> 
</html> 

We notice the changes aren't big compared to the previous solution, but using <meta name="layout" content="..."/> in the views is more compliant to what we already learned when using layouts in views.

Also keep in mind Peter Ledbrook's warning about having layouts nested more than one deep:

Finally, if you do have layouts nested more than one deep (not necessarily a good idea, but that's what grails.org is currently doing) then it's worth noting that every layout must have the <g:layout*/> and <g:pageProperty/> tags, otherwise the contents don't propagate properly.

Sources of the sample Grails application can be found at GitHub.