Loading...

Thursday, March 31, 2011

Grails Goodness: Applying Layouts in Layouts

Grails uses Sitemesh to support view layouts. A layout contains common HTML content that is reused on several pages. For example we can create a web application with a common header and footer. The body of the HTML pages is different for the pages in the web application. But the body can also have a common layout for certain pages. For example some pages have a body with two columns, others have a single column. To reuse these layouts we can use the <g:applyLayout .../> tag. This means we can apply layouts in layouts, which provides a very flexible solution with optimal reuse of HTML content.

Update: another way to apply layouts in layouts based on the comments by Peter Ledbrook.

Let's see how this works with a small sample application. All pages have a header with a logo, search form and main menu. Each page also has a footer with some copyright information. The homepage has a body with five blocks of information, we have product list page with a one column body and finally a product details view with a two column body.

The following diagrams shows the structure of the pages:

Layout homepage

Layout product list

Layout product details

First we create the main layout with the header and footer HTML content. The body content is variable for the pages, so we use <g:layoutBody/> in our main layout.

<%-- File: grails-app/views/layout/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>

For our homepage we define a layout with five blocks. We use Sitemesh content blocks in our layout. This way we can define content blocks in the pages and reference these components in the layouts. To reference a content block we use the <g:pageProperty .../> tag. The name attribute contains the name of the content block we want to reference. Notice we reference our main layout page with <meta name="layout" content="page"/>.

<%-- File: grails-app/views/layout/fiveblocks.gsp --%>
<html>
    <head>
        <meta name="layout" content="page"/>
        <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>

And in the homepage Groovy server page we can use the <g:applyLayout .../> tag and define the content for our Sitemesh content blocks.

<%-- File: grails-app/views/templates/homepage.gsp --%>
<g:applyLayout name="fiveblocks">
    <head>
        <title><g:message code="title.homepage"/></title>
    </head>

    <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>
</g:applyLayout>

We also define a layout with one block:

<%-- File: grails-app/views/layout/oneblock.gsp --%>
<html>
    <head>
        <meta name="layout" content="page"/>
        <title><g:layoutTitle/></title>
        <g:layoutHead/>
    </head>
    <body>
        <div id="main">
            <g:pageProperty name="page.main1"/>
            <g:pageProperty name="page.main2"/>
            <g:pageProperty name="page.main3"/>
        </div>
    </body>
</html>

And a layout with two columns:

<%-- File: grails-app/views/layout/twoblocks.gsp --%>
<html>
    <head>
        <meta name="layout" content="page"/>
        <title><g:layoutTitle/></title>
        <g:layoutHead/>
    </head>
    <body>
        <div id="left">
            <g:pageProperty name="page.left1"/>
            <g:pageProperty name="page.left2"/>
            <g:pageProperty name="page.left3"/>
        </div>

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

Then we use these layouts in the product list and details views:

<%-- File: grails-app/views/templates/productlist.gsp --%>
<g:applyLayout name="oneblock">
    <head>
        <title><g:message code="title.product.list"/></title>
    </head>

    <content tag="main1">
        <h1><g:message code="products.list"/></h1>
        <ul class="product-list">
        ...
        </ul>
    </content>
</g:applyLayout>
<%-- File: grails-app/views/templates/productview.gsp --%>
<g:applyLayout name="twoblocks">
    <head>
        <title>${product.title}</title>
    </head>

    <content tag="left1">
        <h1>${product.title}</h1>

        <p class="product-body">...</p>
    </content>

    <content tag="right1">
        <p>...</p>
    </content>
</g:applyLayout>

Because this is only a small sample application this might seem like overhead, but for a bigger application it is really useful to have reusable layouts. The following screenshots show the layouts in action for a Grails application.

Layout homepage

Layout product list

Layout product details

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