Since EL version 2.2, the following value expression is allowed:
<h:outputText value="#{entry.getRow(column)}" />
Where column would be another variable. Eventually, what seemed to work so well on an outputText, I would like to reuse on an inputText:
<h:inputText value="#{entry.setRow(column)}" />
"setRow" is defined as follows:
public void setRow(String columnName, String content) {
// ...
}
My question is: Does that work? Or rather, I know that doesn't work, since I'm getting an error about how the requested "setRow" method does not exist. So, does what I am trying to do here work in general - and if so, how can it be done?
Thanks for any feedback and best regards
Pascal
That's indeed not a valid expression for a "set" operation. The value expression has to be a fullworthy bean property expression, but you're having there a bean method expression.
You can achieve the particular functional requirement using a Map instead.
private Map<String, String> columns = new HashMap<String, String>();
public Map<String, String> getColumns() {
return columns;
}
with
<h:inputText value="#{bean.columns[column]}" />
On form submit, EL will use Map#put() method to set the value (hence, no setter required for the map) which will then be available in the action method by iterating over the map entries.
Related
Here it is mentioned that
Though only a single ELResolver is associated with an ELContext, there
are usually multiple resolvers considered for any given variable or
property resolution.
For the sake of making it understandable to the one going to answer, I am demonstrating it taking into consideration a custom ELResolver. This is only for illustration purposes. I haven't felt the need for a home-brewed custom ELResolver ever in the projects in which I have worked so far.
Inside the CustomELResolver, getValue() method looks like,
#Override
public Object getValue(ELContext ctx, Object base, Object property)
throws NullPointerException, PropertyNotFoundException, ELException {
logger.log(Level.INFO, "Get Value property : {0}", property);
if ((base == null) && property.equals(SOME_PROPERTY)) {
logger.log(Level.INFO, "Found request {0}", base);
ctx.setPropertyResolved(true);
return SOME_OBJECT;
}
return null;
}
Each value expression is evaluated behind the scenes by the getValue
method. Adding this in faces-config.xml, the custom
resolver is added in the chain of responsibility.
a simple facelet page:
<h:outputText value="#{bean.name}" />
<br />
<br />
<b>Ordered:</b>
<br />
<h:dataTable id="tableId1"
value="#{PROPERTY DECLARED IN CUSTOM RESOLVER CLASS}" var="t">
<h:column>#{t}</h:column>
</h:dataTable>
with
#ManagedBean(name = "bean")
#RequestScoped
public class Bean {
private String name = "Rafael";
// getters & setters
}
When I debug, for this expression value="#{PROPERTY DECLARED IN CUSTOM RESOLVER CLASS}" from the above page, the call delegated to the getValue() in CompositeELResolver, where the CustomELResolver highlighted in red is considered.
Whereas, for this expression
value="#{bean.name}"
a normal ManagedBeanELResolver is considered. Absolutely, no issues with that.
But for the same request, the ELContext was clearly associated with 2 ELResolvers.
Please elucidate as to what the documentation meant in the first paragraph as mentioned above
...a single ELResolver is associated with an ELContext...
You forgot to read the next sentence in the link you refer to.
ELResolvers are combined together using CompositeELResolvers, to
define rich semantics for evaluating an expression.
If you look in the call hierarchy, you'll see 1 (one, a single) FacesCompositeELResolver... So there is one CompositeResolver directly associated with the ELContext.
But you could also read it that effectively only one EL resolver is actually doing the work each time, the one in the 'chain' that says "I've resolved it, here is the result"
I've a WorkflowTask entity with a Map<String, Object> property.
public class WorkflowTask {
private Map<String, Object> properties;
}
The Map has an entry bpm_status which can have a value of Not Yet Started.
I'm displaying a List<WorkflowTask> in a data table and checking for this entry like below:
<p:dataTable value="#{inboxController.list}" var="task">
<p:column>
<ui:fragment rendered="#{task.properties.bpm_status eq 'Not Yet Started'}"><b>#{msg.inbox_new_msg}</b>
</ui:fragment>
</p:column>
</p:dataTable>
I'd like to show a counter with total occurrences of this map entry bpm_status=Not Yet Started in the list.
<p>Amount of new messages: #{inboxController.list.???}</p>
How can I achieve this?
Since EL 3.0, you can use Java8-like stream and lambda operations in EL expressions without the need for Java8 (works on Java7 already).
Your requirement can be achieved as below:
<p>Amount of new messages: #{inboxController.list.stream().filter(task -> task.properties.bpm_status eq 'Not Yet Started').count()}</p>
In case you aren't on EL 3.0 yet, then you need to count it in backing bean itself.
int notYetStartedCount = 0;
for (WorkflowTask task : list) {
if ("Not Yet Started".equals(task.getProperties().get("bpm_status"))) {
notYetStartedCount++;
}
}
In case you're interested, the Java8 equivalent of above would be:
long notYetStartedCount = list.stream().filter(task -> "Not Yet Started".equals(task.getProperties().get("bpm_status"))).count();
This question may be more of the type "conceptual" or "I don't understand JSF".
My scenario:
I have a JSF Page (index.xhtml) where I use a p:accordionPanel (but I don't think it matters what component it is). What I want to do is to set the activeIndexes of it.
<p:accordionPanel multiple="true" activeIndex="#{myController.getActiveIndexesForSections('whatever')}">
// bla bla...
</p:accordionPanel>
And the (simplified) method in the backing bean:
public String getActiveIndexesForSections(String holderName){
String activeSections = "";
for(Section s : sectionMap.get(holderName)){
if (s.isActive())
//add to the string
}
return activeSections;
}
Now this works just fine on a normal page load.
But if I click on a p:commandButton (with ajax=false) (or anything else which "sends" data back to the server I guess) - I get the following exception:
/WEB-INF/tags/normalTextSection.xhtml #8,112 activeIndex="#{myController.getActiveIndexesForSections(name)}": Illegal Syntax for Set Operation
// bla..
Caused by: javax.el.PropertyNotWritableException: Illegal Syntax for Set Operation
After some googling / reading the error message I found that I need a setter.
First of all: I don't want a setter - do I really need one or is there a way to tell JSF I don't want this "behavior".
Second I realized that it's not that "easy" to provide a setter, because my method has a parameter (so public void setActiveIndexesForSections(String name, String activeIndexes) or public void setActiveIndexesForSections(String name)won't work).
What I came up with in the end is:
Create a (generic) "Pseudo-Property-class":
// just a dummy class since the class is recreated at every request
public class Property<T> implements Serializable {
private T val;
public Property(T val) {
this.val= val;
}
public T getVal() {
return val;
}
//no need to do anyhting
public void setVal(T val) {
}
}
Change the bean method:
public Property<String> getActiveIndexesForSections(String holderName){
String activeSections = "";
for(Section s : sectionMap.get(holderName)){
if (s.isActive())
//add to the string
}
return new Property<String>(activeSections);
}
And call it from the index.xhtml:
<p:accordionPanel multiple="true" activeIndex="#{myController.getActiveIndexesForSections('whatever').val}">
// bla bla...
</p:accordionPanel>
This works but obviously is a ugly hack/workaround.
What is the proper way to handle a situation like this? Or is what I'm doing simply completely wrong?
The setter is needed to remember the active indexes as they were when the form is submitted. Basically, you need to bind it as a value expression (with a property), not as a method expression (like an action method), nor to an unmodifiable collection (like activeIndex="#{param.tab}"). Exactly like as with input values. Technically, you're indeed doing it "simply completely wrong" ;)
The requirement is however understood. Given that you're really not interested in the changed active indexes, and thus want to reset them to defaults on every form submit, then you can bypass it by storing the result as a request attribute with help of <c:set>. This way you will fool EL to set it in the request attribute map instead of the intented bean property.
<c:set var="activeIndex" value="#{myController.getActiveIndexesForSections('whatever')}" scope="request" />
<p:accordionPanel multiple="true" activeIndex="#{activeIndex}">
<!-- bla bla... -->
</p:accordionPanel>
Under the covers, it will basically do externalContext.getRequestMap().put("activeIndex", value) as setter operation, which will obviously just work.
Update: upon inspecting the source code of AccordionPanel component, I saw another workaround given the fact that the activeIndex won't be set when the rendered attribute evaluates false. So just alter the rendered attribute to behave exactly that: evaluate false during update model values phase (the 4th phase).
<p:accordionPanel multiple="true"
activeIndex="#{myController.getActiveIndexesForSections('whatever')}"
rendered="#{facesContext.currentPhaseId.ordinal ne 4}">
<!-- bla bla... -->
</p:accordionPanel>
I'm trying to iterate through a List of Map items, i.e. an ArrayList of HashMaps or something similar, and I'm trying to do this in primefaces datatable. This is basically what I'm trying to do:
<body>
<h:form>
<p:dataTable value="#{customerBean.list}" var="map">
<c:forEach items="#{map}" var="entry">
<p:column headerText="#{entry.key}">
#{entry.value}
</p:column>
</c:forEach>
</p:dataTable>
</h:form>
</body>
In this case, customerBean.list is a List<Map<String, String>> and entry is a Map<String, String>.
What I want to do, is create a dynamic amount of columns, based on the amount of entries in a Map<String, String> while using the map entry's key as a header name, and the value as the output. The c:forEach thing seems to work fine when I'm using a hardcoded Map<String, String>, but apparently it can't loop through the var of the p:dataTable. I assume that the program takes precaution to avoid having to loop through Maps of different sizes. So how can I make this work anyway? How can I create an arbitrary amount of columns based on the amount of entries in a Map? Note that I'm a 100% certain that every Map<String, String> is of equal size in my List<Map<String, String>>
EDIT:
Here's my bean source. The code works fine and everything, the problem is just with the loop not willing to go through my map:
#ManagedBean
#SessionScoped
public class CustomerBean {
private List<Map<String, String>> list = new ArrayList<Map<String, String>>();
private Mapper mapper = new Mapper();
public CustomerBean() {
list = mapper.all(); //gets data from database
}
public List<Map<String, String>> getList() {
return list;
}
public void setList(List<Map<String, String>> list) {
this.list = list;
}
}
The problem is unrelated to the Map usage in this context. The problem is that you're trying to get a #{map} variable that's only available when view is being rendered, but you're relying on its value at the moment when view is being built. The latter is performed on an earlier lifecycle phase, so it is basically unavailable when you demand it.
Still, tag handler, or view build tag, like <c:forEach>, is the only way to populate the variable number of columns, as <p:column> is assessed when component tree is being built.
Another thing worth noting is that the backing bean bound to <c:forEach> tag's property, such as items, must be anything but view scoped, like request scoped, otherwise it will be recreated upon every request which will bring unexpected/undesired results, as the demanded bean is not there when you try to access its properties. There are some other setup constellations solving this issue, but they're not the subject of discussion here.
<p:dataTable value="#{customerBean.list}" var="map">
<c:forEach items="#{forEachBean.columnsMap}" var="entry">
<p:column headerText="#{entry.key}">
#{map[entry.key]}
</p:column>
</c:forEach>
</p:dataTable>
Also worth noting that there is a helper <p:columns> component that does roughly the same.
there is a selectOneMenu in my example with a f:selectItems-attribute. The select-items are resolved from my bean like this:
<h:selectOneMenu value="#{bean.value}">
<f:selectItems value="#{bean.selectItems}" var="obj" itemValue="#{obj}" itemLabel="#{obj.name}"/>
</h:selectOneMenu>
The method getSelectItems() in my bean looks like that:
public List<MyObject> getSelectItems() {
List<MyObject> list = new LinkedList<MyObject>();
MyObject obj = new MyObject("Peter");
list.add(obj);
return list;
}
The objects that are displayed are simple objects with a attribute "name".
Nothing special up to this point. But now i change my method to that:
public List<MyObject> getSelectItems() {
List<MyObject> list = new LinkedList<MyObject>();
MyObject obj = new MyObject("<script>alert('xss is bad');</script>");
list.add(obj);
return list;
}
The javascript doesn´t get escaped by MenuRenderer-Class and my page shows me the alert-message.
Is there any cause why the default value of the escape-attribute of SelectItem is "false"?
How can i fix that problem? (I use Mojarra 2.1.7)
The default should indeed not have been false. I've reported it as issue 2747.
In the meanwhile, add itemLabelEscaped="true" to escape it anyway.
<f:selectItems ... itemLabelEscaped="true" />
Note that this is only necessary when you're using GenericObjectSelectItems, i.e. when you're supplying a E[]/List<E>/Map<K, V> instead of List<SelectItem>/SelectItem[]. Also note that escaping is only absolutely mandatory when it concerns user-controlled input (which is fortunately very rarely the case in dropdown values).