Friday, April 20th, 2012

Layout Mechanisms in Grails

Grails is a powerful framework for building cool stuff in Groovy. This post is about structuring the layout of web applications to avoid copy-paste. Grails contains several powerful mechanisms for this purpose. The official documentation on some of them is strangely vague. This note is a snapshot of my own understanding, a work in progress.

One of the main technical aspects of web layout is the DRY rule, Don't Repeat Yourself. No HTML copy-paste, please. There are two main use cases.

The first use case is common fragments. For instance, you may want some object to appear as the same collection of fields in several places. This is similar to using subroutines in programming. Templates is the solution offered by Grails. The template mechanism is well documented and is not further treated here.

The second use case is the inverse of the first one, controlling the overall page layout of some content. It bears resemblance with dependency injection in programming. Grails is based on Sitemesh; perhaps this explains why the Grails documentation in this area is no more than handwaving.

Let's assume we have a Gizmo domain class and a GizmoController with a show action. According to basic Grails conventions the action renders the view gizmo/show.gsp. (This file resides in the grails-app/views directory.) In show.gsp you might find the following snippet.

HTML:

  1.     ...
  2.     "layout""main"
  3.     ...

This means the overall page layout is defined in layouts/main.gsp. Certain fragments, head, title, and body, will be copied from the view and inserted into the layout at points defined by layoutHead, layoutTitle and layoutBody tags. So far this is vanilla Grails.

It's not unusual for an application to have one or two variants of its page layout. The quick and quite dirty fix is to copy the entire main layout once for every variant and then edit. Do resist the temptation, it will get you into trouble. The problem is solved, as usual in computing, by adding a level of indirection.

Let's assume some elements really are present on all pages. For instance: a top banner, some navigation and a standard copyright footer. Let's also assume the area in between is single column in some pages, two columns in others. In such a setting, how would a Gizmo show up on a two-column page?

The head of gizmo/show.gsp should now point to a two-column layout.

HTML:

  1.  
  2.     "layout""twocolumns"
  3.     "language""en"
  4.     A Gizmo
  5.  
  6.   "avoid"
  7.     "col-left"
  8.       ...
  9.       Left column content
  10.       ...
  11.    
  12.     "col-right"
  13.       ...
  14.       Right column content
  15.       ...
  16.    
  17.  

The content tags are special, certainly not standard HTML. Each of them enclose a fragment to be displayed in one of the columns of the page. The ruckus attribute is an example explained later.

The layout meta tag points to layouts/twocolumns.gsp. This is a skeletal view of that layout.

HTML:

  1. "main"
  2.  
  3.    
  4.    
  5.  
  6.  
  7.     "leftcolumn"
  8.       "page.col-left"
  9.    
  10.     "rightcolumn"
  11.       "page.col-right"
  12.    
  13.  

You will have to create the CSS that really generates a two-column layout, but we omit those details here.

The layoutHead tag merges head tags from the view with the head tags of the layout. The layoutTitle tag picks title contents from the view, replacing any title in the layout. Finally each of the two pageProperty tags copies a HTML fragment from a content tag in the view. Tags beginning with g: belong to the Grails name space.

The name attribute of the pageProperty tag begins with a keyword, page in our example. Other available keywords are body, meta, and title. (Frankly, I'm not sure if body really is a keyword or the only tag name that happens to be guaranteed to be unique.)

  • name="page.x" copies from a content tag having an attribute tag="x"
  • name="body.ruckus" copies the ruckus attribute of the body tag of the view, generating avoid in this example. If you add writeEntireProperty="true" the generated output will be ruckus="avoid". The example illustrates that you may invent a non-standard attribute if you like. The only body attributes appearing in the final page are those defined by the main layout.
  • name="meta.language" copies the content attribute of the meta tag where name="language". The result in this example is en
  • name="title" copies the title, duplicating the function of the layoutTitle tag

It is not an error for a pageProperty tag to refer to non-existing content. The tag is silently deleted in such case.

The whole layout is enclosed in an applyLayout tag pointing to layouts/main.gsp. This would be an ordinary layout honoring the usual layoutHead, layoutTitle and layoutBody tags.

What we have done is essentially to introduce an intermediate layout level while keeping a single overall layout. Views are free to select the appropriate layout without causing copy-paste nausea.

Comments are closed.