Description
The Problem:
Rendering a page with a large number of conditionally displayed sub-sections inside of tapestry produces some remarkable ugly looking code; more to the point it tends to gun up your performance because the system is evaluating each and every one of your conditions, even if you, as is quite common, want one and only one section to render.
e.g.
<span jwcid="@Conditional" condition="ognl:today == constants.DaysOfWeek@MONDAY >
Welcome to work. Hope your weekend was good.
</span>
<span jwcid="@Conditional" condition="ognl:today == constants.DaysOfWeek@TUESDAY >
Welcome back.
</span>
<span jwcid="@Conditional" condition="ognl:today == constants.DaysOfWeek@WEDNESDAY >
Only three more days to go!
</span>
...
My recent dive into the bowels of ognl with a profiler (see my previous post on the subject) actually inspired me to set about minimizing the number of ognl calls my applications have to make. So, this was my attempt to dramatically trim down the number of @Conditionals I have to use.
The Solution:
I've implemented (and attached) a Tapestry pseudo switch ... case component pair. To use it, set up an outer switch component, and nest as many case components within it as you want e.g.
<span jwcid="@Switch" switchOn="ognl:today">
<span jwcid="@Case" token="ognl:@constants.DaysOfWeek@MONDAY">
Welcome to work. Hope your weekend was good.
</span>
<span jwcid="@Case" token="ognl:@constants.DaysOfWeek@TUESDAY">
Welcome back.
</span>
<span jwcid="@Case" token="ognl:@constants.DaysOfWeek@WEDNESDAY">
Only three more days to go!
</span>
</span>
Limitations:
1) You can't put anything inside the switch statement except case statement. Anything between the start of the @Switch span and the end of the @Switch span which isn't an @Case, gets ignored completely.
2) The switch statement switches on a string (internally it's just a hashmap). So if you want to switch on something else, convert it to a string before you pass it in.
3) I haven't (yet at least) figured out how to support multiple cases resolving into the same execution block. So if you want Tuesday, Wednesday, and Thursday all to say "The middle of the week sucks", you'll need to have three case blocks, one for each day L. Maybe somebody smarter than I can sort out how to stack them.
Performance Notes:
1) Yes, it is faster than either @Conditionals or @contrib:Choose. Profiled render time for 5 renders of one of my more complex forms was 3.520 seconds for @Choose, 3.323 seconds for @Conditional and 2.523 seconds for @Switch. The speed comes from reducing the number of (hugely) expensive ognl calls it has to make, largely by reducing the size of the expressions e.g. "ognl:today == constants.DaysOfWeek@MONDAY" becomes "ognl: @constants.DaysOfWeek@MONDAY", halving the number of evaluation cycles. Curiously, contrib:Choose is actually marginally slower according to my profiling than just brute force @Conditionals. I've chalked it up as random variance in my profiling for the moment, but its still a head scratcher.
2) I have to evaluate each @Case token to determine if it's a match for the switch, so if you can use a static @Case statement, you'll, again, save yourself an ognl call thus token="4" is dramatically faster than token="ognl:getFour()". Sometimes that's not possible, of course, so I still support the ognl.
General Case Caveot:
As usual, whenever I'm mucking around in the bowels of Tapestry, I'm reminding that I don't think the same way Howard does; so there's a small but very real chance that I built this whole component based on some deeply flawed and fundamentally dangerous assumptions about the framework. With that said though, it does work for me, and I'm planning on rolling it out as part of the current project I'm working on.