Using c:set in FaceletsThis blog is about a common mistake people make with the Facelets c:set tag. The thing about c:set is that the Facelets version isn't like the JSP version. In fact, the Facelets version is just a convenient hack. In JSP, if you write:
<c:set scope="request" name="foo" value="${bar.property}"/>
the JSP TagHandler is responsible for evaluating the EL and putting the result in scope when the page is executed . However, in Facelets, you write:
<c:set name="foo" value="${bar.property}"/>
The first thing you notice is that no scope is specified. That is because the Facelets TagHandler doesn't actually evaluate the EL. Instead, it creates a VariableMapping for the name foo. It is just an alias, and leads to an often overlooked consequence:
- Every time you use ${foo}, ${bar.property} is evaluated.
Using c:set in Facelets
This is generally okay, except when ${bar.property} is an expensive operation such as a database lookup. If you don't want that behaviour, here is a simple Facelets TagHandler which only does the evaluation once:
/**
* An implementation of c:set which evaluates the value expression when
* the variable is set, creating a facelet-scoped attribute.
*
* This tag can also be used as child of a ui:include tag to pass attributes
* into the next facelet scope/context.
*/
public class SetHandler extends TagHandler {
private final TagAttribute var;
private final TagAttribute value;
public SetHandler(TagConfig config) {
super(config);
this.value = this.getRequiredAttribute("value");
this.var = this.getRequiredAttribute("var");
}
/** evaluate and set the attribute in the facelet scope */
public void apply(FaceletContext ctx, UIComponent parent) {
ctx.setAttribute(var.getValue(ctx), value.getObject(ctx));
}
}
There is one serious side effect of using this method, and that is the expression result becomes part of the view state. If your view state is being serialized (e.g. by client side state saving), that could mean trouble if the result is a large set of objects! It is necessary so that the result is available on a postback.
Alternatively, you could write a TagHandler to use the various servlet scopes (the same as the JSP version), but you should be aware that TagHandlers are only instantiated when the view is built, so your expression won't be available on a postback if you use the request scope.
About Roger Keays
|
Roger Keays is an artist, an engineer, and a student of life. He has no fixed address and has left footprints on 40-something different countries around the world. Roger is addicted to surfing. His other interests are music, psychology, languages, the proper use of semicolons, and finding good food.
|
Thank you! Your post was very helpful for me!
Thanks very much Roger, I'm already trying it, but I got some errors during tests, hence you explained my doubts!
Your blog is a great source informations about Facelets!
Congratulations!
Yes, your references will resolve correctly after a postback. Facelets takes care of that with its VariableMappings. Give it a try :)
Exactly, you have complete reason, I'm sorry, just after I wrote the last comment I noted my mistake.
Humm.. another doubt (i'm sorry), because tag handler is instancied once just on build time, my reference to #{defaultBean} (on request scope) will be avaliable to others postbacks normally?
There is no 'page scope' in facelets. ui:param works like c:set by setting up an alias which is used for that composition / decoration.
Thanks Roger,
but ui:param creates the #{defautBean} on page scope, doesn't it?
Another question, Does ui:param work like c:set or fc:set?
Thanks again!
Hey Rafael,
You can do that with ui:param, and it creates all sorts of opportunities for template reuse :)
Hi Roger, great post!
I'd like to know what the best way to pass the reference of the managed bean for my facelets template and continue using it normally, something similar as:
<ui:composition ... template="/masterTemplate.xhtml">
<ui:param name="defaultBean" value="#{myActualManagedBean}" />
</ui:composition>
Then, in the masterTemplate.xhtml I could do something as:
<h:panelGroup rendered="#{defaultBean.currentState}">
...
</h:panelGroup>
Well, using ui:param is it possible that?
Thanks.
Hi Guy. It is true that most components reevaluate EL several times, but that is because they choose to. This is generally necessary in case the result of the expression changes during the lifecycle and happens with JSP as well as facelets.
The most common place to get tripped up with c:set and c:forEach is doing iteration. Each time the EL is used in the loop it is reevaluated, whereas a component such as ui:repeat will only do it as few times as necessary, prior to iteration:
<c:forEach items="${bean.expensiveOperation}" var="item">
${item.stuff}
</c:forEach>
Each evaluation of ${item.stuff} actually evaluates ${bean.expensiveOperation[x].stuff}
Is this also true for facelet's implementation h:outputText? h:outputText with a rendered attribute makes many more than 2 calls on the associated bean; e.g., <h:outputText value="#{foo.property}" rendered="#{foo.renderMe}"/> results in a buttload of calls on foo.getProperty() and foo.isRenderMe()