Tuesday, July 31, 2007

How to brand a Wicket application

Ant has a substitution mechanism called token filtering that is ideal for branding. When you combine this with Wicket's flexible resource model mechanism and its header contribution mechanism, it's quite easy to do sophisticated branding, including the branding of images, CSS and even combining internationalization with branding.


For a good introduction to ant's token filtering, see this blog post.


First I'll give some examples of how to use this technique, then show how it works. In these examples, I'm assuming the two brands we switch between are Company A and Company B.


Simplest Example


Index.java


add(new Label("page.title", getString("page.title.@BRAND@")));

Index.html


<span wicket:id="page.title">Page Title Goes Here</span>

Index.properties


page.title.companya=Welcome to Company A!
page.title.companyb=Welcome to Company B!

In this case, the @BRAND@ token gets substituted by the ant filtering mechanism with the string "companya" or "companya".

Internationalization and branding


This example uses the Wicket resource string interpolation mechanism to achieve internationalization as well as branding.

Index.java


...
add(new Label("page.title", new StringResourceModel("page.title", this, new Model(this))));
...
public String getPageTitleBrand()
{
return getString("page.title.@BRAND@");
}

The StringResourceModel uses the Index class as its model. This means that any variables mentioned in its properties file will be looked up using getters in Index.java.

Index.properties


page.title=Welcome to ${pageTitleBrand}!
page.title.companya=Company A
page.title.companyb=Company B

This will interpolate the variable "pageTitleBrand" into the localized string "page.title", calling the method getPageTitleBrand() in Index.java or its subclasses.

Branding CSS


Sometimes you need to customize the CSS by brand. This can be done as follows. This was inspired by a tip from Al Maw .

Index.java


/* All CSS styles except for those that are branded */
add(HeaderContributor.forCss(Index.class, "Index.css"));
/* Branded CSS */
add(HeaderContributor.forCss(Index.class, "Index_@BRAND@.css"));

Index.css


/* Image supplied by branded CSS */
#top-section {
position: relative;

left: 0;

top: 0;
}

Index._companya.css


#top-section {
background-image: url("/app/images/banner_companya.gif");
}

Index._companyb.css


#top-section {
background-image: url("/app/images/banner_companyb.gif");
}


Branding Images


Images not specified in a style sheet can be dealt with in a similar way to strings. One useful example to call out is a favicon.

FaviconLink.java


/**
* Assumes the wicket:id of the link is "favicon".
* Branded by ant.
* Creates:
* <link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
*/
public class FaviconLink extends ExternalLink
{
public FaviconLink()
{
super("favicon", "/app/images/favicon_@BRAND@.ico");
add(new SimpleAttributeModifier("type", "image/x-icon"));
add(new SimpleAttributeModifier("rel", "shortcut icon"));
}
}


How it works

Ant has a substitution mechanism called token filtering that is ideal for branding. Here's how the following target "filter-sources" works:
  • It copies all the files in the source tree EXCEPT for the Java sources to a temporary location "build.filtered.sources"
  • It copies all the Java sources to the temporary location but does token filtering along the way
  • It replaces the token "BRAND" in the Java code with the value of the ant property "brand"
  • The compile target depends on this target so that this runs first
  • The compile target obtains it sources from "build.filtered.sources"
  • This target depends on the "init" target, which creates the "build.filtered.sources" directory (amongst others)

build.xml

<property name="brand" value="companya" />

<target name="filter-sources" depends="init" description="Does token replacement">
<mkdir dir="${build.filtered.sources}"/>
<copy todir="${build.filtered.sources}">
<fileset dir="${src.main.dir}">
<exclude name="**/*.java"/>
</fileset>
</copy>
<copy todir="${build.filtered.sources}">
<fileset dir="${src.main.dir}">
<include name="**/*.java"/>
</fileset>
<filterset>
<filter token="BRAND" value="${brand}"/>
</filterset>
</copy>
</target>


  • To switch brands, change the "brand" property in the build.xml file, or even better, on pass it on the command line: "ant -Dbrand=companya"

2 comments:

Jonathan said...

Another possibility is to either use styles or variants (see the Wiki for details) in the existing resource locator mechanisms to override html and properties resources to do branding or you could even write your own resource locator. Resource location in Wicket is completely abstracted and quite easy to change. If I was going to do a lot of this, I'd probably write my own resource locator because I'd want to package up these branding resources so they could be easily edited externally and dropped in. It should be reasonably easy to do that in Wicket. -- Jonathan Locke

Julian Sinai said...

Jonathan, it's not obvious to me how to use resource locators for branding. The examples aren't explanatory enough for me.