Calling a method with parameter inside JSTL loop
I have a JSP which needs to print some text which is produced by taking loop iterator and feeding it to anothe开发者_Go百科r object (Spring bean), something like:
<c:forEach var="myVar" items="${myVars}">
<c:out value="anotherObject.getFoo(myVar)"/>
</c:forEach>
Obviously the code above isn't valid as JSTL .
operator only allows parameter-less invocations. I can see following solutions to the problem:
1) Scriptlets
<c:forEach var="myVar" items="${myVars}">
<%
SomeType myVar = (SomeType) pageContext.getAttribute("myVar");
SomeOtherType anotherObject = (SomeOtherType) pageContext.getAttribute("anotherObject");
YetAnotherType result = anotherObject.getFoo(myVar);
pageContext.setAttribute("result", result);
%>
<c:out value="${result}"/>
</c:forEach>
The obvious con here is JSP code pollution and general ugliness.
2) Writing a tag which does whatever is done inside scriptlets. Typical example of over-engineering, yuck!
3) Decompose a collection of myVars
and replace each myVar
with a dynamic proxy, InvocationHandler
of which would add extra parameter-less method to make all getFoo()
calls through anotherObject
. All of that would be done in the controller so JSP would stay clean and myVar
stays the same. But at what price?
I can not add .getFoo()
method to the myVar
because it doesn't fit there and would break the separation of concerns.
It looks like passing parameters will be possible in JSP/EL 2.2, but I'm using Tomcat 6.0.29 which only bundles EL 2.1 API.
Question: can anyone suggest the cleanest approach for this situation?
A simple Java-only "trick-fix" that works in the older JSTL version, too,
and requires no extra taglibs/config/dependencies/frameworks, etc.
is to "wrap" the function you want to call from JSTL
in a class that extends from a Map
class, and override its get()
method.
As a minimal example, if you e.g. want to call the Math.sin()
function from JSTL,
you would define a class:
public class Sine extends HashMap<Double, Double> {
private static final long serialVersionUID = 1L; // Avoids compiler-warning
@Override
public Double get(Object arg) {
Double x = (Double) arg;
return Math.sin(x);
}
}
Then in your Action execute() method, you do:
...
request.setAttribute("sine", new Sine());
...
Then in jsp you can say:
${sine[0.75]}
to calculate the value Math.sin(0.75)
JSTL will treat the variable sine as a Map
, but you can compute and return anything you like from the get() method.
I guess it gets a bit more involved if you have more than one argument to your function, but there should be workarounds for that, too :)
Here's how I've done it at the end.
Instead of passing a collection of SomeType
instances, I am passing a map. Map keys are the same SomeType
s and values are instances of controller-specific inner class, let's call it SomeTypeSupplement
.
SomeTypeSupplement
adds necessary no-arg getters and wires everything together. JSP now iterates over map entries and is able to retrieve the data via JSTL.
This way I avoid Proxy
magic, unnecessary TLDs, keep JSPs tidy and reasonably type-safe.
If you can't abide with scriptlets (your alternative 1), I would create a custom tag for it (your alternative 2) or a custom EL function. I don't agree that it's "over-engineering", it's using the available tools for their intended purpose.
Your alternative 3, though, I don't like. If anything, THAT'S over-engineering plus it will make your system unnecessarily complex and harder for other people to follow. Adhere to the intentions of the specifications and standards you are working within. Don't make things harder or more complex for the sake of it.
Why not just compose your object list in your backend Java code and then just use JSP to display it?
Wanted to add a comment to the last (by time) answer of Rop, but missing "reputation", so here I am with an answer.
I shortly had the same idea with such a map, but tried to make it more generally usable, having a "DynamicMap", DynamicMapCalculator interface and letting you wrap any method call into such a map (without the need to make a new map implementation every time, just using anonymous class instantiation).
This would be the topic, if you are interested: A qu. of style: dynamic map JSTL hack to work around missing parameter function calls
And I would be interested in opinions there: is this something you may do without a bad conscience?
Another option is to use Velocity. It is much nicer than jstl.
精彩评论