How to implement <f:setPropertyActionListener> programmatically - jsf

Basically i'm trying to set a dynamic columns for a <p:datatable>.
The content of one of my columns is a p:commandLink which used to show a dialog for text editing, i have this working like a charm in the XHTML but I need to translate it to Java for dynamic user customization and preferences.
here is what is my XHTML version:
<p:commandLink id="MRepShowButton" update=":form1:display" onclick="EditorDialog.show();" title="Editer le compte rendu">
<f:setPropertyActionListener value="#{exam}" target="#{examenListBean.selectedExamen}" />
</p:commandLink>
and this is my Java version(not working):
CommandLink rapstatelink = (CommandLink)application.createComponent(CommandLink.COMPONENT_TYPE);
rapstatelink.setId("MRepShowButton");
rapstatelink.setUpdate(":form1:display");
rapstatelink.setOnclick("EditorDialog.show();");
rapstatelink.setTitle("Editer le rapport du patient");
ValueExpression target = ef.createValueExpression(elc, "#{exam}", Object.class);
ValueExpression value = ef.createValueExpression(elc, "#{examenListBean.selectedExamen}", Object.class);
//rapstatelink.setActionListener(new SetPropertyActionListenerHandler(**i don't know wht to do here **));
column.getChildren().add(rapstatelink);
table.getChildren().add(column);

You need UICommand#addActionListener(), not UICommand#setActionListener(). The setActionListener() is a deprecated method from JSF 1.x which effectively does a <p:commandLink actionListener="..."> with a ValueBinding.
As to creating the <f:setPropertyActionListener> programmatically, there's unfortunately no JSF implementation independent way for that. Choose either of the following options:
Use the JSF implementation specific class, in case of Mojarra that's the com.sun.faces.taglib.jsf_core.SetPropertyActionListenerImpl:
link.addActionListener(new SetPropertyActionListenerImpl(target, value));
In case of MyFaces that's the org.apache.myfaces.event.SetPropertyActionListener:
link.addActionListener(new SetPropertyActionListener(target, value));
Keep in mind that using JSF implementation specific classes com.sun.faces.* or org.apache.myfaces.* in your own code is a poor practice.
Create a custom ActionListener implementation which does the job. Basically, just copypaste the class' source code from either Mojarra or MyFaces source code into your package. As compared to 1) this has the advantage that your web application does not break when deployed to a Java EE container which ships with the other JSF implementation bundled.
Make use of EL 2.2 feature of the ability to pass method arguments in EL expressions. Then you can just do the job in action or actionListener attribute:
link.setActionExpression(ef.createMethodExpression(elc,
"#{examenListBean.setSelectedExamen(exam)}", Void.class, Exam.class));
(the Exam.class should represent the type of #{exam})
This does effectively the same as
<p:commandLink ... action="#{examenListBean.setSelectedExamen(exam)}" />
Or if you really need to set an action listener:
link.addActionListener(new MethodExpressionActionListener(ef.createMethodExpression(elc,
"#{examenListBean.setSelectedExamen(exam)}", Void.class, Exam.class)));
This does effectively the same as
<p:commandLink ... actionListener="#{examenListBean.setSelectedExamen(exam)}" />

Related

How to update value of EditableValueHolder in action listener?

I have this JSF (Java EE 7, provided by GlassFish 4.1 + PrimeFaces 5.1) form containing database connection information like host name, port number, etc. Also part of this form is a URL field. I want this field to be editable, but I also want to be able to set the value based on the other fields.
To do so I created a button with an action listener where I'm reading the posted data from the request parameter map and generate the new URL value. Then I want to put the new value in the URL field and use that value instead of the posted data. What I tried is to get the component as EditableValueHolder and set the submitted value and render the response. I also tried setting the component's value and calling resetValue.
The best result was the URL field being updates after two clicks.
XHTML:
<p:inputText id="url"
size="50"
value="#{database.url}"/>
<p:commandButton icon="ui-icon-arrowrefresh-1-w"
immediate="true"
actionListener="#{database.createConnectionURL('namingContainer')}">
<p:ajax update="url" />
</p:commandButton>
Bean (using OmniFaces):
UIComponent urlComponent = Faces.getViewRoot().findComponent(namingContainer + "url");
if (urlComponent instanceof EditableValueHolder) {
EditableValueHolder editValHolder = (EditableValueHolder) urlComponent;
editValHolder.setSubmittedValue(urlValue);
}
Faces.getContext().renderResponse();
The immediate="true" is a leftover from JSF 1.x, when it was not possible to process only a specific set of inputs and/or buttons. It was then more than often abused to process only a specific set of inputs and/or buttons instead of to prioritize validation. You'd better not use it when you've JSF2 ajax at hands.
With JSF2 ajax you can just use execute="..." or in case of PrimeFaces process="..." to execute/process only a specific set of inputs/buttons.
<p:inputText id="url"
size="50"
value="#{database.url}" />
<p:commandButton icon="ui-icon-arrowrefresh-1-w"
process="#this"
action="#{database.createConnectionURL('namingContainer')}"
update="url" />
Then you can just update the model value.
public void createConnectionURL(String namingContainer) {
// ...
url = urlValue;
}
Note that I moved back <p:ajax update> into the <p:commandButton>. Perhaps you was mixing with <h:commandButton>.
See also:
Understanding PrimeFaces process/update and JSF f:ajax execute/render attributes
Unrelated to the concrete problem, to deal with components with OmniFaces, better use Components utility class.

JSF Changing language from selectMenu doesn't invoke PostConstruct

I have in the header.xhtml change language selectOneMenu. Which by choosing changes the language properly of any xhtml file. But there are few pages where the language strings are set in the Java class itself which is invoked by the Post-Construct.
Header.xhtml
<h:selectOneMenu value="#{client.language}" onchange="submit()" >
<f:selectItems value="#{client.languages()}" />
</h:selectOneMenu>
In the managed bean, I have ViewScoped and then the PostConstruct. My problem here is- after changing the language from the menu, some string set by the Java are not translated. That's because it is invoked by the PostConstruct which is not invoked when the language is changed. But when I goto that page by clicking the link, then the strings are translated. Its just that as soon as I change language the strings aren't translated.
I think the problem here is because the PostConstruct is not invoked when the language is changed. How do I invoke it?
Your question is basically about the behavior of #PostConstruct. This method will be called once after the bean has been created and after the injection of fields has happened e.g. fields decorated with #EJB and #Resource annotations.
Since you use #ViewScoped bean, then this will be created once per view. Refreshing the page will create a new view, that's why your instance of the #ViewScoped bean will be recreated and you will process the data in the desired language.
Possible solutions:
Mark the bean that handles the view as #RequestScoped.
Use proper i18n internationalization for your output messages. Do not rely on messages being constructed from your managed bean.
In my case, I would use the latter rather than the former. Also, after changing the language, the best option is to fire a new non-ajax request-response cycle to the server.
More info:
How to choose the right bean scope?
Localization in JSF, how to remember selected locale per session instead of per request/view
I have tried to do it like you did in the past with no luck either. Here is how I work around the problem (using JQuery):
<h:selectOneMenu value="#{client.language}" onchange="$(document).find('.submitBtn').click();" >
<f:selectItems value="#{client.languages()}" />
</h:selectOneMenu>
<h:commandButton style="visibility: hidden;" styleClass="jsfHidden submitBtn" action="#{yourpostconstructmethod}"/>

How to add tooltip to f:selectItems

For example the f:selectItems component doesn't support the title attribute in some versions of JSF.
Would it be possible to replace JSF Components by their plain HTML counterparts using JSFC and do something like this?
<select jsfc="h:selectOneMenu" value="#{cc.data}">
<option jsfc="f:selectItems" value="${cc.listItems}" var="item" title="#{item.tooltip}"></option>
</select>
instead of
<h:selectOneMenu value="#{cc.data}">
<f:selectItems value="#{cc.listItems}" />
</h:selectOneMenu>
Doing exactly so, replacing the latter by the above, I'm getting "<f:converter> Parent not an instance of ValueHolder: javax.faces.component.html.HtmlPanelGroup" Facelet TagExceptions
Would it be possible to replace JSF Components by their plain HTML counterparts using JSFC and do something like this
Nope. Ultimately, such a HTML element with jsfc attribute will be turned into a true JSF component in the JSF component tree and only the attributes supported by the component in question would be parsed and set as component attribute. The title attribute isn't among the supported attributes of UISelectItem component. I'm not sure what exactly you mean with "some versions of JSF". The standard JSF API already doesn't support it in first place. JSF spec issue 529 describes this shortcoming and is currently still open.
If you're using JSF 2.2, make use of passthrough attributes. You only need to replace <f:selectItems> by <c:forEach><f:selectItem>, see also Using f:selectItems var in passtrough attribute
<... xmlns:a="http://xmlns.jcp.org/jsf/passthrough">
<c:forEach value="#{bean.items}" var="item">
<f:selectItem itemValue="#{item}" a:title="#{item.tooltip}" />
</c:forEach>
Based on your question history you seem to be not using JSF 2.2 yet. If you can't upgrade, you basically need a custom renderer for <h:selectOneMenu>.
While creating the custom renderer, you could make use of the unused(!) description property of the UISelectItem class. I've answered this before on a similar question targeted at <p:selectManyCheckbox>: Primefaces tooltip for p:selectManyCheckbox or other p:selectMany*/One*.
<f:selectItems ... var="item" itemDescription="#{item.tooltip}" />
Noted should be that creating the custom renderer for <h:selectOneMenu> is a pain, particularly if you intend to be JSF implementation independent. Theoretically, a custom ResponseWriter should be able to catch this, but unfortunately, the <h:selectOneMenu> only passes itself when writing <option>, instead of the UISelectItem in question.
In my case (JSF 2.2 / Mojarra 2.2.14), itemDescription worked out of the box. I.e:
<c:forEach items="#{bean.items}" var="item">
<f:selectItem itemValue="#{item}" itemLabel="#{item}" itemDescription="#{item.tooltip}" />
</c:forEach>

Dynamically generate h:column based on list of hashmaps

In my application I want to display a <h:dataTable> with managed bean properties. Currently this table is created from a List<Folder>. Now I want to change the Folder to something more dynamic. That's because I don't want to change the Folder class if I decide to add another field later. I would just have to add another entry in the Map<String, Object> instead of introducing a new field in Folder.
So, is it possible to bind a List<Map<String, Object>> to the <h:dataTable>?
Is it possible to bind a List of HashMaps to the jsf component h:dataTable?
That's only possible if you generate the necessary <h:column> tags with a view build time tag such as JSTL <c:forEach>.
Here's a concrete kickoff example, assuming that your environment supports EL 2.2:
<h:dataTable value="#{bean.listOfMaps}" var="map">
<c:forEach items="#{bean.listOfMaps[0].keySet().toArray()}" var="key">
<h:column>
#{map[key]}
</h:column>
</c:forEach>
</h:dataTable>
(if your environment doesn't support EL 2.2, you'd need to provide another getter which returns the map key set as a String[] or List<String>; also keep in mind that a HashMap is by nature unordered, you might want to use LinkedHashMap instead to maintain insertion order)
When you're using Mojarra version older than 2.1.18, the disadvantage is that the #{bean} has to be request scoped (not view scoped). Or at least, the <c:forEach items> should refer a request scoped bean. A view scoped bean would otherwise be recreated on every single HTTP request as the <c:forEach> runs during view build time, when the view scope isn't availabe yet. If you absolutely need a view scoped bean for the <h:dataTable>, then you can always create a separate request scoped bean exclusively for <c:forEach items>. The solution would be to upgrade to Mojarra 2.1.18 or newer. For some background information, see also JSTL in JSF2 Facelets... makes sense?
JSF component libraries such as PrimeFaces may offer a <x:columns> tag which makes this more easy, such as <p:dataTable> with <p:columns>.
<p:dataTable value="#{bean.listOfMaps}" var="map">
<p:columns value="#{bean.listOfMaps[0].keySet().toArray()}" var="key">
#{map[key]}
</p:columns>
</p:dataTable>

Passing valueChangeListener method expression into tag file

I have a <h:inputText> with an event listener like following:
<h:inputText valueChangeListener="#{myBean.handle}"/>
I would like to put it in a tag file which is to be used as follows:
<my:itext changeListener="#{myBean.handle}" />
With inside the tag file:
<h:inputText valueChangeListener="#{changeListener}" />
However it's evaluating it as a property instead of as a listener method. How can I pass the listener method into a tag file?
You can by design not pass method expressions as a tag file attribute. You basically need to convert the ValueExpression to a MethodExpression inside the tag file.
For JSF 2.x Facelets, this can be solved using OmniFaces <o:methodParam>.
<o:methodParam name="changeListenerMethod" value="#{changeListener}" />
<h:inputText valueChangeListener="#{changeListenerMethod}" />
However, for old and deprecated Facelets 1.x or JSP 2.x there is no existing solution. The OmniFaces <o:methodParam> is however open source, you should be able to copy and alter it for Facelets 1.x or JSP if necessary.
Note that when you're actually already using JSF 2.x, you could also use a composite component instead. This supports passing method expressions as <cc:attribute method-signature>. For JSF 1.x you can alternatively also create a real custom component, but that's a bit more work than just some XML.

Resources