A new tool for Tapestry?

A new tool for Tapestry?

Lately, while looking at those nice site-map like diagrams (like JSF Config Designer – Navigation Flow Designer in MyEclipse and others for Struts), I thought: why not have something like this in Tapestry?

Actually, I’ve never gotten to like these graphical editors, but I just thought it would be nice to have an overview of what’s going on. So, i’ve done a bit of coding on this and it seems to be achievable.

The idea is this:

  • get all .page and .jwc
  • Search for Page-Action-Direct links
  • Also look at the .html
  • For direct links, get the listener and “follow” the java code to find which page gets activated
  • Create nice graphs!

Here’s an image of what this tool creates right now:



Yellow boxes are components, white are pages and cyan are listeners.

For graph manipulation I used an old jar I had on my pc, called JHotDraw.
I don’t know if it’s still active, and I may look for some other OS library (maybe the one ArgoUML uses – can’t remember its name). I’ll also be looking
for a library to do source code parsing and another one for html parsing (I only use simple String searches in the current version of the tool).

And finally, did you know that Greece has just won the Eurovision song contest ??

Diagrams for Tapestry

During this weekend, I’ve worked on a tool that creates diagrams from Tapestry
applications. It’s been a lot of fun, since I’ve learned how to parse java source files (with ANTLR) and I’ve refreshed my memory on XML+HTML parsing.

Well, here is an even bigger image of what I have so far.

There’s now a lot of stuff in there, and I’ll need to find a way to enhance the visuals. However, you can clearly see all the pages(white) and the components(yellow) of the app, as well as the links between them. Listeners(cyan) called from pages or components are also shown but what’s nice is that you can now see where these listeners forward.

On the tech side, I’ve continued using JHotDraw for the graphs, but have now updated to version 6.0b1 (which however is over 1 year old). I’ll probably take a look at GEF during the next weekend. I now use NekoHTML for html parsing. I decided in favor of Neko due to its license and to the fact that it exposes its services as if it were a normal SAX parser which is really handy. Finally, in order to ‘see’ what the listeners do (in the java code), I had to find a way to parse java source files. No problem there however, since I’ve found a java 1.5 grammar, and created an ANTLR parser quite easily.

Of course, the generated parser is quite generic and I had to create utility methods, such as a) find methods of file, b) find usages of parameter a in method b, e.t.c. I wonder however if there’s a library out there that does such things…

On a final note, I decided that I’ll first port this to an Intellij IDEA plugin, since IDEA users are always complaining about lack of Tapestry support.

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>
 

Tapestry’s Table (cont.)

In a recent blog I described how I customized Tapestry’s contrib:Table, by making the pages links appear both at the top and at the bottom of the table, and by adding an optional form inside the table, in order to allow filtering of the results.

That customization was achieved by creating a custom Table component (just copy/paste the default .jwc and .html and subclass – if needed – the org.apache.tapestry.contrib.table.components.Table class).

Well, I’ve now gone even further by also including a custom TablePages component. This new component is able to display the pages link in a much nicer way, and uses assets for the Next, Back, First, Last links. Finally, I’ve made my custom Table component include a Block at the top ( just after the filter form ), so that I can easily add things there without needing to create new version of this component.

When I get the time, I’ll include all these stuff into TapFX – http://tapfx.sourceforge.net .
Here’s a screenshot of what I’ve got so far:


Thank you Tapestry

Just finished my first commercial app using Tapestry! It’s already installed and running at the client’s intranet and I’ld really like to say a big thank you to this great framework and to all the people at the user and dev list (esp. Mindbridge, Eric, Paul, Howard, Geoff ‘Spindle’ and Michael ‘Palette’ .

I used Tapestry 3.0.3 (a rock-solid version), Hivemind 1.0 for DI (a great 300kb IOC framework), Hibernate (accessing a nice and slick PostgreSQL) and iText (for PDF reports). For Tapestry components, I heavily used the contrib library and (my own) TapFX ( http://tapfx.sf.net ) library and many custom ones (including some DWR enabled one which will shortly find their way into TapFX). The Partial component of tacos was really interesting, but (as I said) I ended up using DWR for AJAX functionality.

I’ll finally get enough time to investigate Tapestry 4.0. I’ve been following the list (and the source code) closely and I’ll now get the time to try it. I may even expand and enhance my tool for generating (navigation) diagrams from Tapestry apps, and who knows, maybe it’ll become an IDE plugin some day.

[OT] Hey, did you know that http://www.chess.gr broadcasts live chess games using my Java Chess Viewer? The url is http://www.chessworld.info/acat05/acat05.html

My Tapestry Inspector has changed!

Hey!
What happenned to my little buddy?
Why did he stop moving?
Why did he stop trying to catch-up with all the resizes I was making?
Grrrrr, I really liked his silly behaviour, it was funny, it was cute…
I liked the way he moved up and down, always heading for the bottom right corner of my Firefox, sometimes hiding, sometimes not, sometimes even scrolling down the browser!

He was full of life!

But now, he sits still wherever you instuct him to. He’s not fooling around
any more.

And we all know who’s responsible for this. It’s his father, Mr Howard!

After making an announcement, he briefly went on to punish his child. A script was removed, some styles applied and the fix was complete.

I can only guess at the child’s last words…
– Hey father, what are you doing? I promise to grow up, I promise to never miss that lower right corner again…

RESPECT THE INSPECTOR

Update with 3 new components

Yesterday, I’ve updated the $TapFX$ project with 3 more components.
I already had them and used them in my project, but i just now managed to add
them to this public library.

The new components are nothing special:

  • ConfirmPageLink and ConfirmDirectLink popup a javascript alert when click, prompting the user for a confirmation in order to go on. The confirmation text is actually taken from the localized resources of the current page.
  • ShowItems displays a list or an array or … anything. You can specify a separator which will be placed in between. The default separator is the comma (,)

2 more components for Tapestry

Just added 2 new components into TapFX ( http://tapfx.sf.net ), Cache and FilteringTable.

The Cache component uses ehcache and allows caching of dynamic content generated in Tapestry pages. Just enclose an html-tapestry fragment with something like

 <span jwcid="@Cache" name="mycache" key="ognl:id"> ... </span>
 

In this example, the component will search for a cache named mycache and then for the id key. If found, it will ignore its body and output the cached content. Otherwise, the content is rendered and output as normal, but also stored in the given cache with the specified key. Each cache properties are controlled from the ehcache.xml found on your classpath.

The FilteringTable component is a drop-in replacement for contib:Table ( which it uses under the hood ). I’ve already discussed this component in previous blogs and its main purpose is to visually enhance its replacement. It shows navigation controls (using assets for the next, previous, first, last links) both at the top and at the bottom of the table. It can also display a small Form with a TextField near the top navigation control which can be used in order to allow the user to filter the data presented.

My next plans are to provide components for displaying cache statistics and to further enhance FilteringTable with export capabilities.

Installation info is available at the project’s web site.

Allowing Tapestry components to contribute CSS

It has always been easy to contribute javascript from a component to a page in Tapestry. The Body component included in Tapestry framework is responsible for gathering all contributed javascript and placing it either in body’s onLoad or exactly after the body tag.

However, instructing Tapestry to add a CSS, either from within a custom component or while in the middle of a page has always been an issue. I even remember that there was a patched Tapestry version which allowed such functionality.

Well, after reading Using JavaScript to dynamically add Portlet CSS stylesheets by Mark McLaren, a simpler solution can be implemented.

So, I just created a new Style component (included in TapFX v0.30 library) and here’s the code:
style.js

 function tapfx_addStyleSheet(styleUrl)
 {
 	if(document.createStyleSheet) 
 	{
 		document.createStyleSheet(styleUrl);
 	}
 	else 
 	{	
 		var styles = "@import url('" + styleUrl + "');";
 		var newSS=document.createElement('link');
 		newSS.rel='stylesheet';
 		newSS.href='data:text/css,'+escape(styles);
 		document.getElementsByTagName("head")[0].appendChild(newSS);
 	}
 }

Style.script

 <?xml version="1.0"?>
 
 <script>
     <include-script resource-path="style.js"/>
     <input-symbol required="yes" key="css" class="java.lang.String" />
     <body>
        <![CDATA[
         tapfx_addStyleSheet('${css}');
           ]]>
     </body>    
 </script>
 

Style.jwc

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE component-specification PUBLIC
   "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
   "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
 <component-specification allow-informal-parameters="no" allow-body="no">
     <description>Adds a css to the page</description>
     
     <parameter name="css" type="org.apache.tapestry.IAsset" direction="in" required="yes">
         <description>The css asset to add.</description>
     </parameter>    
 
 </component-specification>
 

Script.html

 <span jwcid="@Script" script="/net/sf/tapfx/components/style/Style.script"
 	css="ognl:page.requestCycle.requestContext.getAbsoluteURL(css.buildURL(page.requestCycle))"/>
 

I should just mention that all these work for Tapestry v3.01-3.03 for the moment.

Easy Insert for Tapestry

After getting some positive feedback for a previous blog, I decided to add the EasyInsert component into TapFX.

In the latest version (0.15, just get the jar and drop it into your classpath),
you’re able to use the following:

  • ${user.name} . This will become: <span jwcid=”@Insert” value=”ognl:user.name”/>
  • <div class=”${global.class}”>content</div> . This will become: <div jwcid=”@Any” class=”ognl:global.class”>content</div>
  • <div jwcid=”@MyDiv” class=”${global.class}”>content</div> . This will become: <div jwcid=”@MyDiv” class=”ognl:global.class”>content</div>

Anyway, I hope this proves useful to all.