Kontakt

Extending Template Lookup Mechanism

Posted on Freitag, 6th August, 2010

As you probably know Tapestry’s localization support provides localized component templates.¬†A page or a component may have several locale-specific templates. When rendering a component Tapestry will search for a localized version of each component template and use the closest match. For example you could have MyPage_fr.tml for French users, MyPage_de.tml for German users and MyPage.tml for all other users.

In my Tapestry workshops I’m often asked how to extend the template lookup mechanism. Some Tapestry based applications have a use case to provide different flavors of a single page template. Imagine you want your app do be used on mobile phones. In this case you need to provide an alternative rendering for your pages. It is almost impossible to ¬†create a template which looks good on standard web browser and mobile devices. Thus you need to provide different templates for a single page.

Extending Template Lookup Mechanism

This is the point at which Tapestry’s extensibility comes into play. As you know you can easily override almost every part of Tapestry by replacing a particular service. Now let me show you how to extend Tapestry’s template lookup mechanism in Tapestry 5.2. The discussed mechanism was also available in prior versions but it was a part of the internal API.¬†You can use the¬†¬†internal API only at your own risk because¬†we can change in the future releases. In 5.2 this mechanism became a public API. Now it is safe to extend it without to fear any upgrade problems. Let’s see it.

The key part of Tapestry’s template lookup mechanism is the ComponentTemplateLocator service which is used to locate both page and component templates. The service implementation is a chain-of-command and can be configured by ComponentTemplateLocator instances. Tapestry contributes two implementations of ComponentTemplateLocator to the chain:

  • Default: Searches for a template on the classpath
  • Page: Searches for page templates in the application context (root folder of your WAR file)

¬†Now let’s contribute a command which will locate a page template based on a classifier. Such a classifier can be a string identifying the flavor of the template to render the page. For example if you want to render the page MyPage on iPhone you may provide a classifier like iphone to retrieve the template MyPage_iphone.tml.¬†If the client is on the Palm phone, the¬†classifier palm can be used¬†to¬†retrieve the template MyPage_palm.tml. If the classifier is not available, the default template MyPage.tml will be rendered.

Let’s create a per-thread¬†service named¬†TemplateClassifierSource, which is responsible for identifying the client and providing a corresponding classifier. The implementation details are not interesting.

public interface TemplateClassifierSource {
    String getClassifier();
}

Our implementation of ComponentTemplateLocator will use the TemplateClassifierSource service to identify the proper template for the page. In the following example we first retrieve the page class name and resolve the page name from it. Then we calculate the name of the template resource and load the resource from context root. We also try to find a localized version of the template. Thus we can provide templates like MyPage_iphone_de.tml. If no template was found we return null. In this case the next command in the ComponentTemplateLocator chain will try to locate the template.

public class ClassifierPageTemplateLocator
            implements ComponentTemplateLocator {
   @Inject
   @ContextProvider
   private AssetFactory contextAssetFactory;

   @Inject
   private ComponentClassResolver resolver;

   @Inject
   private TemplateClassifierSource classifierSource;

   public Resource locateTemplate(ComponentModel model,
                                  Locale locale) {
      String className = model.getComponentClassName();

      if (!className.contains(".pages.")) {
         return null;
      }

      String logicalName = this.resolver
            .resolvePageClassNameToPageName(className);

      String path = String.format("%s_%s.%s",
            logicalName,
            this.classifierSource.getClassifier(),
            TapestryConstants.TEMPLATE_EXTENSION);

      Resource contextRoot =
            this.contextAssetFactory.getRootResource();

      Resource resource = contextRoot.forFile(path);

      if (resource != null) {
         return resource.forLocale(locale);
      }

      return null;
   }
}

Contributing ComponentTemplateLocator

Next we contribute our ComponentTemplateLocator before the locator with id Page.

public class AppModule {

   public static void bind(ServiceBinder binder) {
      binder.bind(TemplateClassifierSource.class,
                  TemplateClassifierSourceImpl.class);
   }

   public static void contributeComponentTemplateLocator(
      OrderedConfiguration<ComponentTemplateLocator> config) {

      config.addInstance("Classifier",
                         ClassifierPageTemplateLocator.class,
                         "before:Page");
   }
}

Comparing To JSF Renderers

If you are familiar with JavaServer Faces technology, you will recognize that the described mechanism is comparable to JSF Component Renderers. Both Tapestry and JSF allow you to render a component in different ways. The big difference is the fact that JSF Renderers don’t have templates. You code the rendering of the component inside your Java classes. Isn’t it the reason why we don’t use Servlets anymore? However, in Tapestry you define the look and feel of a component in a template. Another disadvantage of JSF Renderers is that they are limited to components. There is no way in JSF to achieve the same for a Facelet page.

Summary

In this article you learned how to extend Tapestry’s template lookup mechanism. Note that we covered only the lookup of page templates from application context root. You can enhance the mechanism to locate component templates on the classpath.

The described mechanism is very flexible. Instead of coding a classifier into the template name, one could store the specific templates into a subfolder and use the classifier as folder name.

 

  1. Alfie
  2. Igor Drobiazko

 

avatar

Your name

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 

Tapestry 5 Blog - Copyright © 2009 - Eclectic Theme by Your Inspiration Web - Powered by WordPress