Passing action methods into Facelets tags
The problem
JSF, via Facelets, has various mechanisms to easily reuse view content. One of those is the Facelets tag, which allows one to reuse markup and/or components.
One notorious problem with these Facelets tags is that you can't directly pass action methods (method expressions) into them. There's a workaround where you break the expression into two parts, the base (which should be a value expression) and the name of the method as a plain string. Inside the tag these two are then combined again, e.g. via the array notation syntax. See e.g.
Another approach is to create a small helper tag that converts the value expression in some way into a method expression. There have been a couple of implementations of this idea:
- Creating composite controls with JSF and facelets
- How the button is created and how it acts (see ActionMapperTagHandler at the bottom of the page)
Finding a solution
In this blog I would like to present another variant on the tag handler idea, which doesn't require nesting the content and also doesn't parse any embedded EL expression manually. It supports EL parameters provided by the calling view. It's not perfect however, since it does not support parameters being passed into the method from the target component (e.g. an ActionEvent
for an actionListener
).
The idea is to wrap the value expression (which represents the original method expression passed into the Facelets tag) inside a custom method expression. This method expression simply gets the value from the embedded value expression, which as "side-effect" executes this method.
The resulting method expression has to be stored somewhere. The request scope would be an option, but then it would be directly available outside the Facelets tag, which isn't a good example of encapsulation.
An alternative is the javax.el.VariableMapper
, a "magic" little thing that is able to scope value expressions to the duration of a Facelets tag (it's magical since remember that the tag doesn't exist any more after the component tree has been build). This however requires a value expresion again, so in an odd twist we wrap the method expression that we just created in a value expression again.
So, how can this work? Wasn't the entire point of this exercise to go to a method expression? Well, as it turns out a value expression can be accepted wherever a method expression is required, iff this value expression directly returns a method expression.
E.g. the Apache EL implementation's org.apache.el.parser.AstIdentifier contains the following code fragment that accomplishes this:
// case A: ValueExpression exists, getValue which must // be a MethodExpression VariableMapper varMapper = ctx.getVariableMapper(); ValueExpression ve = null; if (varMapper != null) { ve = varMapper.resolveVariable(this.image); if (ve != null) { obj = ve.getValue(ctx); } } // (case B omitted) if (obj instanceof MethodExpression) { return (MethodExpression) obj; }
Code
Without further ado, here's the code for the TagHandler that implements all the wrapping:
public class MethodParamHandler extends TagHandler { private final TagAttribute name; private final TagAttribute value; public MethodParamHandler(TagConfig config) { super(config); this.name = this.getRequiredAttribute("name"); this.value = this.getRequiredAttribute("value"); } public void apply(FaceletContext ctx, UIComponent parent) throws IOException { String nameStr = name.getValue(ctx); // The original value expression we get inside the Facelets tag, that's actually the method expression passed-in by the user. ValueExpression valueExpression = value.getValueExpression(ctx, Object.class); // A method expression that wraps the value expression and uses its own invoke method to get the value from the wrapped expression. MethodExpression methodExpression = new MethodExpressionValueExpressionAdapter(valueExpression); // Using the variable mapper so the expression is scoped to the body of the Facelets tag. Since the variable mapper only accepts // value expressions, we once again wrap it by a value expression that directly returns the method expression. ctx.getVariableMapper().setVariable(nameStr, ctx.getExpressionFactory().createValueExpression(methodExpression, MethodExpression.class)); } }
The rather trivial adapter that wraps the value expression is shown below. The main method of interest here is invoke()
. Note how unfortunately the params
parameter has to be ignored.
public class MethodExpressionValueExpressionAdapter extends MethodExpression { private static final long serialVersionUID = 1L; private final ValueExpression valueExpression; public MethodExpressionValueExpressionAdapter(ValueExpression valueExpression) { this.valueExpression = valueExpression; } @Override public Object invoke(ELContext context, Object[] params) { return valueExpression.getValue(context); } @Override public MethodInfo getMethodInfo(ELContext context) { return new MethodInfo(null, valueExpression.getExpectedType(), null); } @Override public boolean isLiteralText() { return false; } @Override public int hashCode() { return valueExpression.hashCode(); } @Override public String getExpressionString() { return valueExpression.getExpressionString(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof MethodExpressionValueExpressionAdapter) { return ((MethodExpressionValueExpressionAdapter)obj).getValueExpression().equals(valueExpression); } return false; } public ValueExpression getValueExpression() { return valueExpression; } }
The tag handler has to be registered in a *-taglib.xml file, e.g.:
<tag> <tag-name>methodParam</tag-name> <handler-class>com.example.MethodParamHandler</handler-class> <attribute> <name>name</name> <required>true</required> <type>java.lang.String</type> </attribute> <attribute> <name>value</name> <required>true</required> <type>java.lang.String</type> </attribute> </tag>
Using the tag handler
So the above takes care of implementing the converting tag handler. Let's take a look at how we would use this.
Suppose we have a Facelets tag in the following using-view fragment, where the action attribute binds to a method in a backing bean taking an EL parameter:
<p:dataTable id="table" value="#{someBean.someValues}" var="someValue"> <my:deleteActionColumn action="#{someBean.delete(someValue)}" update="table" /> </p:dataTable>
The tag could then be defined as shown below. The interesting bit is the <my:methodParam>
tag, which converts the value expression "action" to the method expression "actionMethod". The tag can be placed at many locations, but reasonable choices would be as the first child of the root or directly above the component using it. I choose the latter here.
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui" xmlns:my="http://example.com/my" > <p:column styleClass="some_style"> <my:methodParam name="actionMethod" value="#{action}"/> <p:commandLink id="someId" action="#{actionMethod}" process="@this" update="#{update}"> <h:graphicImage value="/icons/delete.png" alt="#{i18n['something.delete']}" title="#{i18n['something.delete.tooltip']}" width="15" height="15" /> </p:commandLink> </p:column> </ui:composition>
This Facelets tag too again has to be explicitly declared in a *-taglib.xml, e.g.:
<tag> <tag-name>deleteActionColumn</tag-name> <source>faces/tags/deleteActionColumn.xhtml</source> <attribute> <name>action</name> <type>javax.el.MethodExpression</type> </attribute> </tag>
Any other alternatives?
At first sight the composite component mechanism would seem to be an alternative, as it explicitly supports passing in method references. However, the composite component isn't a true substitute for Facelets tags. Namely, it inserts a parent component in the tree instead of simply its content. This doesn't work for tables, panel groups, and all other kinds of components that require children of a specific type or in a specific order to be present.
You can sometimes do some magic with composite components by using the componentType
attribute (see e.g. JSF composite component binding to custom UIComponent), but this only gets you so far and sometimes you really simply need the raw body of a tag to be inserted into the using page.
Another alternative solution, arguably the one and only Real Solution™, is having this fixed by Facelets. Indeed, many years ago there was an issue created for this at the Facelets JIRA: http://java.net/jira/browse/FACELETS-263 with a similar issue even having a patch submitted for it: http://java.net/jira/browse/FACELETS-273. Unfortunately, neither of those issues ever got resolved.
Hopefully the alternative solution provided here is useful for those situations where the existing workarounds are not completely satisfactory. Do note again that the solution presented here is also not ideal, but seems to work nicely in a rather straightforward way for action methods that don't use framework provided parameters, but can optionally take user supplied EL parameters.
An implementation of this is readily available in the new OmniFaces library, from where you can find the documentation and the full source code. There's also a live demo available.
Arjan Tijms
Comments
Post a Comment