Custom Theming a Grails Application – Our Approach

We recently changed the core Secret Escapes project to allow us to create white label integrations. Essentially, we needed to add the ability for our application to apply different themes depending on the provided affiliate or subdomain.

The end result of this work is that you get a differently branded experiences, while retaining much of the same functionality when visiting differently branded pages.

For example, this is the front page of http://www.secretescapes.com

and this is the front page of http://teletext.secretescapes.com

In this post, I will talk about some of the details of this custom theming work and some of the implementation details and decisions we took.

Deciding on a Theme.

Our application had two ways in which a theme could be overridden: 1) via an url parameter, 2) based on the subdomain and 3) based on the default theme set in the user.

Setting a theme via url parameter

In our application, we have an affiliate domain object that keeps track of all the information needed for this custom theming. To override the theme of a page, all you need to do is to pass in an affiliate parameter.

So, http://www.secretescapes.com/instant-access/teletext is different from http://www.secretescapes.com/instant-access/es because we map an affiliate name to the last parameter.

Implementation wise, this simply requires us to set the affiliate based on the params object. i.e


def affiliate = Affiliate.findByName( params.affiliate ) ?: DEFAULT_AFFILIATE

Setting a theme via a subdomain

We also set up different theming rules based on the subdomain in which the call is set.

To identify the subdomain, we create a filter that does the following:

     def filters = {
        all(uri:'/**') {
            before = {
                String domain = request.getHeader('Host')?.trim() ?: ''
                def affiliate =  Affiliate.findByDomain(domain);
                if ( affiliate ) {
                    affiliateAccessService.setAffiliateAccess( affiliate.id as Long, session)
                }
                return true
            }

            after = { model ->

                if ( affiliateAccessService.getAffiliateAccess(session) ) {
                    model?.affiliate = affiliateAccessService.getAffiliateAccess(session);
                }
                return true
            }
        }
    }

Essentially, all we’re doing here is retrieving the host name and determining if an affiliate matches the given domain. If there is a match, we inject the affiliate matched to the model.

Setting a theme via the user

Finally, the user has a property called affiliate that might override the final theme that gets displayed. This is simply a value stored in the domain object itself.

Overriding CSS Styling.

The joy of background images

One of the tricks our designer came up with to easily override images is to reduce the number of actual <img/> tags used in the site. Rather, things that needed to be overwritten like Logos and other elements were set as background images in CSS. Using this technique, we are able to change the image on the page by simply overriding the background image property in the file.

Using Amazon S3 / CloudFront

Making a change in the theme CSS file should not require a redeployment or restart of the server. To make things easier, we kept all our white label information in a separate folder structure hosted on Amazon S3. The folder structure looked like this:

    |- themes
         |- affiliate1
               |- theme-v1.css
               |- images
         |- affiliate2
               |- theme-v3.css
               |- images

for each affiliate, we would create a separate folder that will contain an override css.

All the images used by this affiliate would be stored in the images folder.

We also had a simple versioning system for the css files so that we could set different theme files during development.

Overriding the CSS

Finally, to override the styling in a default CSS them, we simply added this to our layout files.

<g:if test="${ theme }">
   <link rel='stylesheet' type='text/css' href='${theme.cssLocation}'/>
</g:if>

Styling Third Party Components.

In addition to changing the look of components in our site ( the easy part ), we also needed to enable custom theming of all our mailing templates and Paypal customisation.

Paypal.

Paypal allows you to override individual colours and logos in their form by providing hidden variables to their site. This was not complex, but it was tedious. We ended up with a domain object that looked like this:

These values could be overwritten by whoever was creating a new custom skin. Whenever we request a PayPal payment form, these values would then be passed forth.

Mail Templates / Notifications.

Perhaps the most radical changes needed in our application came to the mailing templates that we used. We use Cheetahmail to ensure mail delivery. Prior to this project, we kept different templates on the server:

In this implementation, we would pass mail merge fields into a pre-made HTML template and Cheetahmail will send out an email after merging these fields.

Cheetahmail provided a slightly restrictive template engine, and we felt that it would not be possible for us to fully change the mail content on their servers.

Therefore, we moved the content of all the Cheetahmail templates into gsp templates on the Grails side. These templates would also be able to display different bits of content based on the theme that are selected.

So when we need to send out an email, first the mail merge contents and the theme contents get passed into a GSP view. The resulting HTML is then sent to a dumb template (i.e., with little or no merge fields or logic ) on the Cheetahmail servers and passed on to the user via email.

In addition, since we are no longer able to distinguish between the different type of emails sent by the Cheetahmail server, we had to build an in-house statistics system to keep track of how many emails of each different type were sent to Cheetahmail.

Additional Considerations.

Static Text / Sidebars

As part of this custom theming project, we needed to be able to change certain bits of text within our site ( terms and conditions, privacy policy, etc ). As we had only few bits to change, we simply created properties within our themes to keep track of these. One can imagine that if this gets to be too complex or convoluted, proper content management systems like Weeceem might come into play.

Feature Flipping.

We also had to turn certain features ( Facebook comments, for example ) off for some of our affiliates and themes. We ended up building a set of variables within our affiliate object that control the availability of certain features ( invite a friend, Facebook comments, opt-in, etc ) and either hid them or displayed them based on their availability.

Custom Layouts and Views.

We have not had the need to define custom layouts for different themes yet. But this might change in the future as we work with more partners. I imagine that we would use a template mechanism similar to that of the CSS styles, by which we would keep different versions of views and layouts and render them based on the chosen theme. Something like:


<meta name="layout" content="${theme}/main" />

Summary.

As you can see, adding the ability to custom theme a grails application is not that complicated, but might involve substantial changes to a system. We were fairly surprised at having to rewrite our entire notification system to accommodate some simple changes in generated HTML and CSS text.

In general, many of these changes are really specific to a system, and it seems fairly difficult to generalise it to a plugin.

I hope some of these tips have been useful when you’re designing a new grails project that might need to be custom themed.

2 thoughts on “Custom Theming a Grails Application – Our Approach

  1. Pingback: An Army of Solipsists » Blog Archive » Esta semana en Grails (2011-34)

  2. Pingback: An Army of Solipsists » Blog Archive » This Week in Grails (2011-34)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s