|
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
== [EMAIL PROTECTED] > 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:@[EMAIL PROTECTED]"> Welcome to work. Hope your weekend was good. </span> <span jwcid="@Case" token="ognl:@[EMAIL PROTECTED]"> Welcome back. </span> <span jwcid="@Case" token="ognl:@[EMAIL PROTECTED]"> 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 == [EMAIL PROTECTED]” becomes “ognl: @[EMAIL PROTECTED]”, 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. |
package components; import org.apache.tapestry.AbstractComponent; import org.apache.tapestry.IMarkupWriter; import org.apache.tapestry.IRequestCycle;
public abstract class Case extends AbstractComponent {
private String fToken;
protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {
}
public String getToken() {
return fToken;
}
public void setToken(String token) {
fToken = token;
}
public abstract String getElement();
}
package components;
import java.util.HashMap;
import org.apache.tapestry.AbstractComponent;
import org.apache.tapestry.IBinding;
import org.apache.tapestry.IMarkupWriter;
import org.apache.tapestry.IRender;
import org.apache.tapestry.IRequestCycle;
public abstract class Switch extends AbstractComponent {
private String fSwitchOn;
private boolean initialized = false;
private HashMap fMap;
public String getSwitchOn() {
return fSwitchOn;
}
public void setSwitchOn(String switchOn) {
fSwitchOn = switchOn;
}
private void initialize() {
if (initialized)
return;
fMap = new HashMap();
IRender[] foo = this.getBody();
for (int x=0;x<foo.length; x++) {
IRender one = foo[x];
if (one instanceof Case) {
Case ca = (Case) one;
IBinding bind = ca.getBinding("token");
String value = bind.getString();
fMap.put(value, one);
}
}
initialized = true;
}
@Override
protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {
initialize();
Case block = (Case) fMap.get(fSwitchOn);
if (block != null)
block.renderBody(writer,cycle);
}
public abstract String getElement();
}
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
