Monday, August 27, 2007

Branding a Wicket application, take 2

In this post, I showed how to brand a Wicket application using Ant filtering. In response to Jonathan's comment I thought about it some more, and found that Wicket's style feature is exactly what I needed.


The style feature is for skinning your application, which for a corporate site is another way of saying branding. Having set the style property in the session, it is then available to you anywhere in the application where you need to use it. In addition, the property file resolver automatically takes style into account. There is also some support built into ResourceReference to enable resolution of resources other than property files.


Simplest Example (repeated from my previous post on branding)


Index.java (old)


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

Index.java (new)


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

Index.html (old and new are the same)


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

Index.properties (old)


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

Index_companya.properties (new)


page.title=Welcome to Company A!

Index_companyb.properties (new)


page.title=Welcome to Company B!

What has changed is that there are now two properties files, distinguished by "_brand", and Wicket automatically resolves them.



An alternative to the above (also from my previous post under the heading "Internationalization and branding")


Index.java (old)


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

Index.properties (old and new)


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

Index.java (new)


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

This is an alternative to creating separate properties files for each brand. The style property in the session gives you the flexibility to put all the branding strings in one property file. This can be useful if you have complex strings containing lots of variable interpolations, or if you want to minimize the number of properties files that need to be sent to a translator.



Branding CSS (also from my previous post)


Index.java (old)


/* 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 (old and new)


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

Index._companya.css (old and new)


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

Index._companyb.css (old and new)


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

Index.java (new)


/* All CSS styles except for those that are branded */
add(HeaderContributor.forCss(Index.class, "Index.css"));
/* Branded CSS */
add(HeaderContributor.forCss(new ResourceReference(Index.class,
"Index.css", null, this.getSession().getStyle())));

Here we make use of Wicket's support for styles in ResourceReference. Of course, it would be nicer if Wicket's support for the resolution of CSS (and other resource file types) was as automatic as for property files. That is, if you use the constructor ResourceReference(Index.class, "Index.css") it should automatically look for Index_brand.css.



Branding Images (also from my previous post)


FaviconLink.java (old)


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"));
}
}

FaviconLink.java (new)


public class FaviconLink extends ExternalLink
{
public FaviconLink()
{
super("favicon", "/app/images/favicon_"+Session.get().getStyle()+".ico");
add(new SimpleAttributeModifier("type", "image/x-icon"));
add(new SimpleAttributeModifier("rel", "shortcut icon"));
}
}

Note that in this case we use Session.get().getStyle() because ExternalLink does not have instance access to the session. Fortunately, it's easy enough to get at it.



How it works


A convenient way to initialize the the session with the brand is to set it in web.xml:


web.xml (new)


<servlet>
<servlet-name>quickstart</servlet-name>
<servlet-class>wicket.protocol.http.WicketServlet</servlet-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>wicket.quickstart.QuickStartApplication</param-value>
</init-param>
<init-param>
<param-name>brand</param-name>
<param-value>companya</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

QuickStartSession.java (new)


...
protected QuickStartSession(final WebApplication application)
{
super(application);
// Read brand from web.xml
String brand = application.getWicketServlet().getInitParameter("brand");
if (brand == null)
{
brand = "companya";
}
this.setStyle(brand);
}
...

However, I wanted to continue to use ant filtering to set the brand. But now, instead of filtering the Java sources, I only need to filter the web.xml:


web.xml (new)


...
<init-param>
<param-name>brand</param-name>
<param-value>@BRAND@</param-value>
</init-param>
...

build.xml (new, see comments in my previous post)


...
<target name="filter-sources" depends="init" description="Does token replacement">
<copy todir="${build.filtered.sources}">
<fileset dir="${basedir}">
<include name="web.xml"/>
</fileset>
<filterset>
<filter token="BRAND" value="${brand}"/>
</filterset>
</copy>
</target>

<target name="war" depends="compile,filter-sources" description="Creates a WAR file for this package">
<war destfile="${build.dir}/${final.name}.war"
webxml="${build.filtered.sources}/web.xml">
...

This will copy web.xml from the project root (same location as build.xml) to the location build.filtered.sources and in the process replace the branding token. Then the war target builds the WAR using the filtered web.xml.

2 comments:

shetc said...

Nice article Julian! I have a couple of questions:

1) Do the property files with the string resources have to live in the same folder as the associated java file?

2) How do you use the FaviconLink?

Thanks,
Steve

Julian Sinai said...

Steve,

1. No, the string resources can live in as few or as many resource files as you like. Wicket has a sophisticated resource loading algorithm. In the limit, you can have one resource file for the whole application (e.g. myApplication.properties), or at the other extreme, one per component. See the docs for ComponentStringResourceLoader or the Pro Wicket book for an explanation.

2. Here's an example:

MyPage.java constructor:
add(new FaviconLink());

MyPage.html head section:
<link wicket:id="favicon" />