Use ${variable} instead of Insert component in Tapestry 4

Not long ago, some ppl in the Tapestry mailing list were asking for a shorcut when
using the Insert component.
They prefered writing ${user.name} instead of <span jwcid="Insert" value="ognl:user.name"/>

This functionality can easily be added into Tapestry 4. It’s a matter of finding which class to extend and which configuration point to set. So, let’s begin with the java code.
In this case, we’re going to extend org.apache.tapestry.parse.TemplateParser, overriding the method public TemplateToken[] parse(char[] templateData,
ITemplateParserDelegate delegate,
Resource resourceLocation)

In that method, we’re going to convert templateData to a String and then replace
all occurances of ${something} into <span jwcid=”@Insert” value=”ognl:something”/> . As seen, we’ll be adding a default binding of ognl, unless there’s already a binding.
In this way, ${message:home} will simply become <span jwcid=”@Insert” value=”message:home”/>. After that, we’ll just delegate to the base class. So here’s some code:

EasyInsertTemplateParser.java

 package andyhot.tap4.parse;
 
 import org.apache.hivemind.Resource;
 import org.apache.tapestry.parse.ITemplateParserDelegate;
 import org.apache.tapestry.parse.TemplateParseException;
 import org.apache.tapestry.parse.TemplateParser;
 import org.apache.tapestry.parse.TemplateToken;
 
 /**
  * For replacing ${something} into
  * &lt;span jwcid="@Insert" value="ognl:something"/&gt;
  * @author andyhot
  */
 public class EasyInsertTemplateParser extends TemplateParser
 {
     public TemplateToken[] parse(char[] templateData,
                                  ITemplateParserDelegate delegate,
                                  Resource resourceLocation)
     throws TemplateParseException
     {
         String template = new String(templateData);
         int pos = 0;
         boolean changed = false;
         do
         {
             pos = template.indexOf("${", pos);
             if (pos >=0)
             {
                 int pos2=template.indexOf("}", pos);
                 if (pos2>=0)
                 {
                     changed = true;
                     String oldValue = template.substring(pos + 2, pos2);
                     String newValue = applyBinding(oldValue);
                     template = template.substring(0, pos)
                     	+ "&lt;span jwcid=\"@Insert\" value=\"" + newValue + "\"/>"
                     	+ template.substring(pos2 + 1);
                 }
             }
         }
         while (pos>=0);
         if (changed == true)
         {
             System.out.println("Expanded: " + resourceLocation.getPath());
             //System.out.println(template);
         }
         return super.parse(template.toCharArray(), delegate, resourceLocation);
     }
 
     private String applyBinding(String value)
     {
         int pos = value.indexOf(":");
         if (pos<0)
         {
             return "ognl:" + value;
         }
         else
         {
             for (int i = 0; i < pos; i++)
             {
                 char c = value.charAt(i);
                 if ((c>='a' && c<='z') || (c>='A' && c<='Z'))
                     continue;
                 return "ognl:" + value;
             }
             return value;
         }
     }
 }

hivemodule.xml

 <?xml version="1.0"?>
 
 <module id="EastInsert" version="1.0.0" package="andyhot.tap4">
 
   <service-point id="EasyTemplateParser" interface="org.apache.tapestry.parse.ITemplateParser">
 
     Parses a template into a series of tokens that are used to construct a component's body.
 
     <invoke-factory model="threaded">
       <construct class="andyhot.tap4.parse.EasyInsertTemplateParser">
         <set-object property="factory" value="instance:org.apache.tapestry.parse.TemplateTokenFactory"/>
       </construct>
     </invoke-factory>
 
   </service-point>
 
   <service-point id="EasyTemplateSource" interface="org.apache.tapestry.services.TemplateSource">
 
     A smart cache for localized templates for pages and components.  Retrieves a template on
     first reference, and returns it on subsequent references.
 
 
     <invoke-factory>
       <construct class="org.apache.tapestry.services.impl.TemplateSourceImpl">
         <set-service property="parser" service-id="EasyTemplateParser"/>
         <set-service property="delegate" service-id="tapestry.parse.TemplateSourceDelegate"/>
         <set-object property="contextRoot" value="infrastructure:contextRoot"/>
         <set-service property="componentSpecificationResolver" service-id="tapestry.page.ComponentSpecificationResolver"/>
         <set-object property="componentPropertySource"  value="infrastructure:componentPropertySource"/>
         <event-listener service-id="tapestry.ResetEventCoordinator"/>
       </construct>
     </invoke-factory>
 
   </service-point>
 
   <contribution configuration-id="tapestry.InfrastructureOverrides">
 	 <property name="templateSource" object="service:EasyTemplateSource" />
   </contribution>
 
 </module>
 

andyhot

6 thoughts on “Use ${variable} instead of Insert component in Tapestry 4

  1. Nathan, you’re right. My code was meant to be a quick illustration of how to add such kind of functionality into Tap4. When I get back from vacation (in Corfu) i’ll add a complete sollution.

    Regarding your question, I don’t think that this is allowed in Tap4. But maybe we could again extend Tapestry to make this possible. I may also have a look at this when I’m back!

    Have fun…

  2. Nice work, but I still prefer the basic idea of keeping a clean HTML template. ${variable} might seem nifty to a developer, but to everyone else it is awkward at best.

  3. This is great. I am currently using a customized version of Tapestry 3.0.x which includes this feature. This will speed up my future transition to Tap4.

    However, your code seems to suffer from the same bug as my 3.0.x code. The problem is that OGNL syntax includes curly braces (see http://www.ognl.org/2.6.7/Documentation/html/LanguageGuide/collectionConstruction.html#mapConstruction). Thus, you really need to actually match the braces to find the end of the expression, instead of just searching for the first close-brace character.

    I had added another feature to Tap3, where it would auto-copy components if I specified them once in the .page file but then used them multiple times in the .html file. (Normally that’s not allowed in Tapestry, but in my custom version it just duplicates the component automatically.) Do you know if that feature is part of Tap4, or if it could be easily added?

Leave a Reply

Your email address will not be published. Required fields are marked *

Read previous post:
Tapestry’s Table (cont.)

Tapestry's Table (cont.) In a recent blog I described how I customized Tapestry's contrib:Table, by making the pages links appear...

Close