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.

11 comments:

Manuel said...

Great post! Thank You!!!

pledbrook said...

Hi MrHaki,

Good account. A couple of suggestions: I would use in the layouts rather than in the views. Then, if you have layouts nested more than one deep, you keep all the applyLayout tags in views rather than having one in a view and some in layouts.

Also, I think it's best to stick to the standard evaluation of layouts for views (i.e. <meta>, convention, application.gsp) to give you flexibility and consistency with the way people expect layouts to be applied to views.

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 and tags, otherwise the contents don't propagate properly.

Regards,

Peter

mrhaki said...

@Peter Ledbrook: thank you for your comment. Unfortunately some of the content you mention in the comment have been filtered out by Blogger's comment engine. Like I would use ... in the layouts. And every layout must have the ... and ... tags.

pledbrook said...

Doh! "I would use <g:applyLayout/> in the layouts rather than in the views"

and "then it's worth noting that every layout must have the <g:layout*/> and <g:pageProperty/> tags, otherwise the contents don't propagate properly."

skurt said...

Great Post, I was always thinking if there is a way to do something like this, but never tried! Thanks!

Anonymous said...

Thanks a great post. I especially liked the diagrams :)

Anonymous said...

Thanks for the article, but the following code

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

will not work, I mean that head part will not be processed properly in the main layout.
You should change it to the following

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

gexhouse2 said...

This site is to much immpressiv and atracted also good job an keepit up.

logo company in Pakistan
Logo design online in Pakistan

Muhammad Amir said...

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. website design westchester ny

Fiverr Work said...

Likes significantly boost the attractiveness of one's website and as well as the suitable variety count; quite a few of us could possibly be drawn to your account. The providers giving these merchandise and solutions, how to buy instagram likes will give you with non-automated or robotic likes and provide you with authentic Instagram likes. The great depth relating to this is usually that these are definitely straight away despatched for the account therefore you do not have to show up at that substantially. And when you are a company, elevating your Instagram likes will very likely be valuable.

albina N muro said...

Let's see how this works with a small sample application. All pages have a header with a logo, search form and main menu. wordpress ticket system

Post a Comment