(Quick Reference)

8 Creating A Theme - Reference Documentation

Authors: Marc Palmer (marc@grailsrocks.com)

Version: 1.0.RC2

8 Creating A Theme

Creating your own a theme is relatively easy. It is a matter of creating the standard GSP layouts tailored to your styling and defining the Resources that the theme requires.

You can create a theme from scratch or fork an existing one using the create-theme and fork-theme scripts provided.

Once you have done so you can edit the GSP layouts and resource definitions, add static resources, bespoke tags etc as necessary.

Each Theme depends on a specific UI Set. The UI Set templates are resolved such that the application or theme can override the template provided by the UI Set. This means your theme can be based on a reusable UI Set and still tweak some rendering.

Theme layouts render one or more content zones.

8.1 Requirements

The Themes must conform to the following specification.

Required Layout Names

All themes must supply a minimum set of standard layouts.

Failure to do so means that plugins that rely on any missing layouts will break. However themes can provide a superset of the required themes - so this list of required layouts is kept to a bare minimum.

Each required layout also has a list of required zone names|guide:themeRequiredZones], so that plugins and apps know what they can/should provide for each.

The required layouts are:

  • home
  • main
  • dialog
  • dataentry
  • report
  • sidebar

The requirements and user expectations of each layout are set out below.

If a theme does not support a given layout or zone this in itself is not a major problem - application developers will choose the theme that does what they require, and may provide a replacement layout in their application.

However all themes should support all the layouts and their zones if they wish to be used as general-purpose themes.

Rendering of Resources

All Theme layouts must use the Resources plugin mechanisms to render their resources and those of the UI Set they use. The UI Set will typically include the resources in needs on demand, but your Theme must include the required r:layoutResources tags. This is handled for you automatically if you use the head theme tag.

Rendering of Title

The title theme tag is used by GSP pages to set the current page title using standard Platform Core i18n resolution mechanism (using the p:text rules).

As such, the Theme layouts must use this information to render the <title> tag of the page and are responsible for rendering the <h1> or equivalent heading in the content of the page automatically

GSP pages do not know where to place the title of the page shown in the content area, as this is up to the Theme layout.

As such they simply specify what the title is, and the layout has to "do the right thing" by creating a relevant div or heading tag.

Default content

Themes allow you to supply default content for each zone that shows off the theme at its best, so that application developers can get a taste for how their app might really look when using your theme correctly.

This is also useful for content that is typically the same in all your layouts (i.e. navigation, footers) that you still would like the application developer to be able to override by supplying content for that zone or overriding the default GSP.

To define your own default content, simply place GSP templates in:

/grails-app/views/_themes/<ThemeName>/default/_<zone-name>.gsp

for defaults that apply to any Theme layout or:

/grails-app/views/_themes/<ThemeName>/default/<LayoutName>/_<zone-name>.gsp

for defaults that are specific to one of your named theme layouts.

8.2 Creating Theme Layouts

Theme layouts are straightforward Sitemesh GSP layouts.

However they use the theme namespace tags to render multiple content regions (zones) supplied by the plugin or application GSP page being rendered.

This means that GSP pages can e.g. provide main and sidebar content, without knowing how those will be laid out in the final page.

Here's an example Theme layout using minimal structural markup:

<!DOCTYPE html>
<html>
    <theme:head/>
    <theme:body>
        <ui:logo/>
        
        <div id="nav">
            <theme:layoutZone name="navigation"/>
        </div>
        
        <div id="user-nav">
            <theme:layoutZone name="user-navigation"/>
        </div>
        
        <div class="container">
            <h1><theme:layoutTitle/></h1>
            
            <ui:displayMessage/>
            
            <div id="second-nav">
                <theme:layoutZone name="secondary-navigation"/>
            </div>

            <div class="content">
                <theme:layoutZone name="body"/>
            </div>

            <div id="sidebar">
                <theme:layoutZone name="sidebar"/>
            </div>
            
            <div id="footer">
                <theme:layoutZone name="footer"/>
            </div>
        </div>
    </theme:body>
</html>

You can see here that the theme:layoutZone tag is used to render each chunk of content supplied by the GSP, and decorate it with the markup required to work with the CSS to achieve the desired layout - and the content in those zones is unaware of these requirements.

The head and body tags are optional but simplify layouts significantly. If you do not use these, there are more tags that you will need to use, such as layoutTitle which handles the theme-aware i18n friendly title mechanism, and resources to include the Theme's resources, and the regular r:layoutResources calls to get the Resources plugin to render the actual resource dependencies.

See the head, body and layoutTitle theme tags for details of the behaviour of these utility tags.

A GSP page would define content for this layout using multiple calls to theme:zone:

<html>
    <head>
        <theme:layout name="sidebar"/>
        <theme:title text="my.page.title"/>
    </head>
    <body>
        <theme:zone name="body">
            <p>This is the body of my page</p>
        </theme:zone>

        <theme:zone name="sidebar">
            <p>New items added today...</p>
        </theme:zone>
    </theme:body>
</html>

Notice that not all the zones have content defined. Content zones that have no content defined in the page will fall back to defaults supplied by the application, theme, default theme or specimen text. See Content Zones for details.

Providing default zone content

Themes can and should provide attractive default zone content. This is automatically rendered if the application GSP page does not provide content for a zone.

To provide default content for a zone, you simply supply a GSP at a path of the form:

/grails-app/_themes/<themeName>/default/_<zoneName>.gsp

This would provide fall-back zone content for all layouts in your theme. You can provide a more specific fall-back per layout in your theme by saving the GSPs at the following location:

/grails-app/_themes/<themeName>/default/_<layoutName>/_<zoneName>.gsp

8.3 The Required Zones Of Each Theme Layout

Each required layout has a list of required zones. The layout must render these zones if provided, but it can be adaptive to the presence or absence of content for the zones - for example if there are three promotional content zones but content for only two is provided, the layout could adapt to this using ifZoneContent.

Required layout: home

This layout is intended to represent the "home page" of a site. Typically with a banner area and marketing panels, of which the layout should support at least three. It could support more using ifZoneContent.

ZoneDescription
bodyThe main body content
navigationThe primary navigation options (home etc.)
user-navigationThe user navigation options (log in etc.)
footerThe footer
bannerThe primary marketing banner area
panel1A marketing content area
panel2A marketing content area
panel3A marketing content area

Required layout: main

This layout is intended to represent normal site content pages. Typically with just a body.

ZoneDescription
bodyThe main body content
navigationThe primary navigation options (home etc.)
user-navigationThe user navigation options (log in etc.)
footerThe footer

Required layout: dialog

This layout is intended to represent a "full screen" form such as a login. Typically with just a body, the body is used as the content within the "full screen" form.

ZoneDescription
bodyThe main body content
navigationThe primary navigation options (home etc.)
user-navigationThe user navigation options (log in etc.)
secondary-navigationThe secondary navigation options
footerThe footer

Required layout: report

This layout is intended to represent report results with pagination, e.g. a scaffolded list view.

ZoneDescription
bodyThe main body content
navigationThe primary navigation options (home etc.)
user-navigationThe user navigation options (log in etc.)
secondary-navigationThe secondary navigation options
footerThe footer
paginationThe report pagination controls

Required layout: dataentry

This layout is intended to represent data entry screens, e.g. a scaffolded create/show/edit view.

ZoneDescription
bodyThe main body content
navigationThe primary navigation options (home etc.)
user-navigationThe user navigation options (log in etc.)
secondary-navigationThe secondary navigation options
footerThe footer

Required layout: sidebar

This layout is intended to represent a body area that also has a sidebar.

ZoneDescription
bodyThe main body content
sidebarThe sidebar content
navigationThe primary navigation options (home etc.)
user-navigationThe user navigation options (log in etc.)
secondary-navigationThe secondary navigation options
footerThe footer

8.4 Defining and Including Theme Resources

Themes can provide global static resources and per-layout resources by convention.

Using the Resources plugin, Themes simply define Resource modules with the following name convention:

    // Global resources for all layouts
    'theme.YourThemeName' {
        
    }
    
    // Per-layout resources, optional
    'theme.YourThemeName.main' {
        // resources here
    }

    'theme.YourThemeName.home' {
        // resources here
    }

The Theme API will automatically pull these in as required when your Theme layout uses the head theme tag or explicitly calls resources theme tag.

8.5 Reusable Theme Templates

The Themes API includes a mechanism to reuse Theme template fragments using a standard template resolution mechanism.

This provides a standard by which Themes can expose these templates while making it easy for applications to override them if necessary.

One very common use of this is to have a common page header and/or footer section used across multiple layouts provided by your Theme. Using the layoutTemplate theme tag instead of g:render ensures your templates follow the Theme conventions.

8.6 Branding

The application of site branding within a theme is obviously very important.

Out of the box, when a user switches to your Theme, all the existing information about their application should be used. The site name, the URLs, and logos should be instantly incorporated to decorate their application with your theme, while retaining their site's branding.

If you do this correctly, the tools provided in Platform UI and Platform Core allow Themes to provide diverse branding while minimizing the need for developers to customize or fork the theme.

The tools available include:

  • The smart logo:uiTags tag
  • Customizable UI Set template for the logo:uiTags tag
  • The p:siteName, p:siteLink, p:siteURL and p:organization tags from Platform Core
  • Resource overrides for CSS tweaks in UI Sets and Themes

8.7 Configuration

There are a few configuration options that affect details such as which Theme is selected by default.

KeyDescription
plugin.platformUi.theme.defaultConfig value to the name of the theme to be used by default if no other theme is set on the current request. If this is not specified, it will default to the first theme installed that is not the Platform Ui example theme called "_default". If there are no others it falls back to this "_default"
plugin.platformUi.theme.layout.defaultIf a page requests a layout that is not supported in the current theme, it will fallback to the "default layout" set in config. This defaults to "main" so that unknown theme layouts result in something reasonable.
plugin.platformUi.themes.layout.mapping.*Used to map layout names to alternative layout names - for example to remap all uses of "dialog" layout to your own "fullscreenDialog" layout for all GSPs that use the "dialog" layout, set plugin.platformUi.themes.layout.mapping.dialog = 'fullscreenDialog'. This applies globally if there is no theme-specific layout remapping.
plugin.platformUi.themes.<ThemeName>.layout.mappingAs for the global equivalent, this allows remapping of layouts per-theme, so that e.g. your plugin or app can make a specific theme use a different layout X when layout Y is requested.
plugin.platformUi.themes.<ThemeName>.ui.setDefines the UI Set that ThemeName requires. e.g. plugin.platformUi.themes.MyTheme.ui.set = 'Bootstrap'
plugin.platformUi.themes.<ThemeName>.ui.<className>.cssClassAllows you to override the CSS classes used for UI Set elements on a per-theme basis. For example you may have two theme variants - one with large input fields and one with small fields. You would set ui.MyBigFormTheme.input.cssClass = 'input-xlarge' in one and use another value in the other. This overrides the class configuration provided by the UI Set itself.