JSF/Facelets: set `action` attribute to a dynamically evaluated string
-
11-09-2019 - |
Question
In my JSF/Facelets application, I want to dynamically generate a breadcrumb trail from a list of page IDs using a custom tag:
<foo:breadcrumbs trail="foo,bar,baz"/>
This should generate something like:
<h:commandLink action="foo" ... />
<h:commandLink action="bar" ... />
<!-- (etc.) -->
My code looks something like this:
<ui:repeat value="#{fn:split(trail, ',')}" var="key">
<h:commandLink action="#{key}" ... />
</ui:repeat>
The problem with this code is that #{key}
is interpreted as a method binding. However, I just want the string value of #{key}
to be returned as the navigation outcome. How can I achieve this?
The only thing I could think of was creating a dummy managed-bean that has an outcome
field and an action handler, and invoke it like so:
<h:commandLink action="#{dummy.click}" ...>
<f:setPropertyActionListener target="#{dummy.outcome}" value="#{key}" />
</h:commandLink>
with the dummy class defined like so:
public class Dummy {
private String outcome;
public String click() {
return outcome;
}
public void setOutcome(String outcome) {
this.outcome = outcome;
}
public void getOutcome() {
return outcome;
}
}
That seems ugly though, and I don't know if it would work.
Solution
Since asking this question, I've discovered an obvious solution that is very simple to implement.
A method that is the target of a JSF action must accept no arguments and return a String.
It turns out that the String class already has a method that matches this signature - toString()
!
So, I've changed my UI loop to the following:
<ui:repeat value="#{fn:split(trail, ',')}" var="key">
<h:commandLink action="#{key.toString}" ... />
</ui:repeat>
This allows the dynamically evaluated key
to be the JSF action outcome, and doesn't require any additional classes or ugly hackery.
OTHER TIPS
A couple of ways spring to mind.
Option 1
Stick with commandLink and read the var
directly from the request map in the action binding:
public String click() {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext extContext = context.getExternalContext();
Map<String, Object> reqMap = extContext.getRequestMap();
return (String) reqMap.get("uirepeatVar");
}
(Where the repeater has the attribute var="uirepeatVar"
.)
Option 2
Switch to an outputLink and build GET links on the server:
public List<String> getViewUrls() {
List<String> views = Arrays.asList("/index.xhtml", "/idtable.xhtml");
List<String> urls = new ArrayList<String>();
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
ViewHandler viewHandler = application.getViewHandler();
for (String view : views) {
String url = viewHandler.getActionURL(context, view);
urls.add(url);
}
return urls;
}
View:
<ui:repeat value="#{breadcrumbBean.viewUrls}" var="url">
<h:outputLink value="#{url}">#{url}</h:outputLink> <br />
</ui:repeat>
Why don't you create a custom component which generates the h:commandLink objects programmatically? It would probably be the 'cleanest' solution.