This question already has an answer here:
How to call JSF backing bean method only when onclick/oncomplete/on... event occurs and not on page load
(1 answer)
Closed 6 years ago.
Recently I ran into a problem with one of my . I have a separate xhtml containing conditionally rendered icons/links to show different kinds of popups. This xhtml is basically a container for specific kinds of popups that I can include on different pages. The rendered conditions (and a passed ui:parameter) make sure only the relevant icons/links get shown depending on where this xhtml is included. This prevents me of having to write lots of different ui:includes on each page.
For some popups it's necessary to prepare some data, which is done via the onclick attribute of an a4j:commandLink. Then, the oncomplete will show the actual popup like so:
<a4j:commandLink render="clientGroupMemberInfoPopup" rendered="#{assignmentDO.clientGroupMember}"
onclick="#{clientInfoBean.registerGmClientGroupMember(assignmentDO.gmClientGroupMemberDO)}"
oncomplete="RichFaces.ui.PopupPanel.showPopupPanel('ClientInfo')">
<h:graphicImage value="/img/icons/icon_info_sm.png" rendered="#{!printFriendly}"/>
</a4j:commandLink>
The corresponding bean:
#ManagedBean
#ViewScoped
public class ClientInfoBean {
#EJB
private ClientService clientService;
#Getter
#Setter
private ClientContextDO clientContextDO;
#Getter
#Setter
private GmClientGroupMemberDO gmClientGroupMemberDO;
#Getter
#Setter
private Long clientId;
public void registerGmClientGroupMember(final GmClientGroupMemberDO aGroupMember) {
gmClientGroupMemberDO = aGroupMember;
clientContextDO = clientService.findByClientId(gmClientGroupMemberDO.getClientId());
}
}
In this case above the rendered condition of the a4j:commandLink evaluates to true. However... the onclick is evaluated every single time, on every page this xhtml is included, once the rendered condition evaluates to true. Even when the page is still loading and nobody has clicked on anything yet!
Why? And what's the best way to prevent this? There's some relatively heavy db-stuff being done to prepare all the info necessary for the popup. I only want this stuff done the moment someone actually clicks on the link for the popup, not during page rendering phases.
There IS a duplicate of this question, I'm sure but I cannot find it. I'll remove this answer when BalusC flags it as such.
The onclick is for executing javascript, not accessing a server-side method. So the EL in it is evaluated as a value expression, not a method expression. So the output is considered as javascript. Consequently it is just evaluated at render time and re-evaluated when clicked.
The solution is to change the onclick to action
<a4j:commandLink render="clientGroupMemberInfoPopup" rendered="#{assignmentDO.clientGroupMember}"
action="#{clientInfoBean.registerGmClientGroupMember(assignmentDO.gmClientGroupMemberDO)}"
oncomplete="RichFaces.ui.PopupPanel.showPopupPanel('ClientInfo')">
<h:graphicImage value="/img/icons/icon_info_sm.png" rendered="#{!printFriendly}"/>
</a4j:commandLink>
Related
I am new to JSF and I am struggling with dynamicaly rendering included pages. My code looks like this:
MenuBean
#ViewScoped
public class MenuBean implements Serializable {
private MenuItem[] menuItems = new MenuItem[] {
new MenuItem("page_1", "/page_1.xhtml"),
new MenuItem("page_2", "/page_2.xhtml"),
};
private String selectedItemLabel;
//...
}
MenuItem
public class MenuItem implements Serializable {
private String label;
private String page;
//...
}
index.xhtml
<ui:repeat var="menuItem" value="#{menuBean.menuItems}">
<h:panelGroup rendered="#{menuBean.selectedItemLabel eq menuItem.label}" layout="block">
<h:outputText value="#{menuBean.selectedItemLabel}" />
<ui:include src="#{menuItem.page}" />
</h:panelGroup>
</ui:repeat>
The result is 2 buttons rendered. Whenever I click any button the label inside conditionaly rendered panelGroup appears but included page doesn't. If I change 'menuItem1' var from first ui:repeat it works but it is really unpredictable. For example if I hardcode setSelectedItemLabel parameter to 'page_1' then when I click to button_1 page_1 is displayed but even if I click to button_2 page_2 (!?) is displayed...
You're facing a view build time vs view render time problem. This is essentially the same problem which is already answered in detail in JSTL in JSF2 Facelets... makes sense? In that answer, replace "JSTL" with "ui:include". To the point, the <ui:repeat> runs during view render time, while the <ui:include> has already run during view build time before.
You'll understand that the only solution is to replace the <ui:repeat> by another tag which runs during view build time, such as JSTL <c:forEach>.
Note that I don't guarantee that it will solve the concrete functional requirement which you've in mind. Using JSTL may have undesirable "side effects" (which are however explainable, understandable and workaroundable).
See also:
How to ajax-refresh dynamic include content by navigation menu? (JSF SPA)
In my actual project I have noticed that the method that populates the ui:repeat tag, is being invoked when there is a post call, even though the ui:repeat is not part of the submitted form.
I have been trying to check againts the jsf documentation if that is the way it should work, with no success.
Is it supposed to work this way?
Thanks in advance.
Sample code:
When the button is clicked the method anotherBean.getCollection is being invoked:
<h:form id="firstForm">
<h:commandButton action="#{someBean.someAction}"/>
</h:form>
<h:form id="secondForm">
<ui:repeat var="product" value="#{anotherBean.populatCollection}" >
<!-- CODE -->
</ui:repeat>
</h:form>
In first place, a getter method shouldn't be populating the value at all. A getter method should, as its name says, just return the already-populated value.
You're not terribly clear on the concrete functional requirement, i.e. when exactly did you intend to populate the value, but one way would be moving the populating logic to the #PostConstruct of #{anotherBean}.
#ManagedBean
#ViewScoped
public class AnotherBean {
private List<Something> getCollection; // Terrible variable name by the way.
#PostConstruct
public void init() {
getCollection = populateItSomehow();
}
public List<Something> getGetCollection() {
return getCollection; // See, just return the property, nothing more!
}
}
See also:
Why JSF calls getters multiple times
So, it looks like ui:repeat tag is invoking the methods assigned to its value argument when a post is done, no matter if the post is done from another form.
Thanks for the help.
Update: for those flagging this to be closed as a duplicate, the supposed duplicate question is nothing like what I am asking. My problem is I do not know until render time what the question set will be, how many questions there will be or what the question types will be so I cannot use the technique described in the "possible duplicate" answer.
Part of our JSF 2.x application has a requirement to render sets of questions to the user where the questions and the question types are not known until run-time. e.g we have something like (getters/setters omitted for clarity) :
public class QuestionSet {
private List<Section> sections;
}
public class Section {
private String sectionTitle;
private List<Question> questions;
private SectionStatus status; // e.g. UNANSWERED, CURRENTLY_ANSWERING,ANSWERED, COMPLETED
}
public class Question {
private String questionText;
private QuestionType questionType; // E.G TEXT, RADIO, LIST, CHECKBOX
private List<String> options; // for RADIO/LIST/CHECKBOX types
private List<String> answers;
}
We need to render each section in a seperate div, depending on it's status (e.g. UNANSWERED would display a div with just the title, ANSWERED would display a div with the section title and a green tick mark, and CURRENTLY_ANSWERING would render a div with the section title and then each question with the appropriate input control based on the question type.
The questions are also dynamic during the run - e.g. if a user answers yes to a radio button question, this may prompt further sub-questions.
I am currently doing this using a binding, i.e.
<h:panelGroup binding = "#{bean.panelGroup}" />
and within the bean's getPanelGroup creating the component tree by hand usin things like HtmlPanelGroup, HtmlOutputText, UIInput with ValueExpressions etc. which works fine but on reading some of BalusC's answers, particlarly to this question: How does the 'binding' attribute work in JSF? When and how should it be used? I am wondering if there is a "better" approach?
One of the things that concerns me is that the getter is called during RECREATE_VIEW for reasons explained in the linked question (after invoking the method referred to in the binding) so unless I take steps to, in RECREATE_VIEW phase, just return the component I created in the last RENDER_RESPONSE phase, this introduces unnecessary expense of recreating something I've just created.
In this case, it also seems pointless that JSF calls my setter to set the thing I just gave it in the getter for the bound property. (my bean is View scope as I will need to use ajax for some of the functionality our users require)
Thoughts/opinions (Especially from the ever helpful BalusC) greatly appreciated...
I don't see much reason to use component binding in this case. You can decide in your view what to render and how. You can have <ui:fragment>/<c:if> to conditionally render elements basing on question type, <ui:repeat>/<c:forEach> to handle the question set, etc.
So, if I understand the workflow correctly, your question set will be determined in e.g. post constructor method:
#PostConstruct
public void init() {
questionSet = service.get();//get it somehow
}
Then you'll have a set of sections and each of these section will contain questions, or answers, and validity is to be checked via AJAX. If I understand you right, then you can have the following view:
<h:form id="q-set">
<ui:repeat value="#{bean.questionSet.sections}" var="section">
<div>#{section.title}</div>
<div class="#{section.status eq 'UNANSWERED' ? 'section-unanswered' : ... }"/>
<ui:fragment rendered="#{section.status eq 'ANSWERED' ?}"><div class="tick"/></ui:fragment> ...
<ui:fragment rendered="#{section.status eq 'ANSWERED' ?}">
<ui:repeat value="#{section.questions}" var="question">
<div>#{question.title}</div>
<ui:fragment rendered="#{question.type eq 'RADIO'}">
<h:selectOneRadio value="#{question.answers[0]}" validator="...">
<f:selectItems value="#{question.options}" var="opt" itemLabel="#{opt}" ... />
<f:ajax ...>
</h:selectOneRadio>
</ui:fragment>
...
</ui:repeat>
</ui:fragment>
</ui:repeat>
</h:form>
It looks like you are going to have too much logic/conditions in your view.
What about generating the view programmatically on the Java side ?
For tricky parts you may resort to JavaScript and JSON.
How can I create toggle buttons in JSF?
Basically, I need two buttons "in" and "out". They essentially call the same managed bean, but as a result of every click the one should be disabled and the other should be enabled and vice versa. How can this be done? Should I use ajax functionality?
Just have a boolean property which you inverse in action method and use exactly that property in the disabled attribute of the both buttons, inversed.
Kickoff example:
#ManagedBean
#ViewScoped
public class Bean {
private boolean enabled;
public void toggle() {
enabled = !enabled;
}
public boolean isEnabled() {
return enabled;
}
}
With
<h:form>
<h:commandButton value="Enable" action="#{bean.toggle}" disabled="#{bean.enabled}" />
<h:commandButton value="Disable" action="#{bean.toggle}" disabled="#{not bean.enabled}" />
</h:form>
Ajax is technically not necessary. Feel free to add <f:ajax> to both buttons to improve the user experience though.
A #ViewScoped bean is in turn very necessary. A #RequestScoped one would be trashed on end of request and recreated in next request, hereby causing the boolean to be reinitialized to default and thus seemingly fail to work after second click, because JSF will as part of safeguard against tampered/hacked requests also check the disabled (and rendered) attribute before actually invoking the action.
See also:
How to choose the right bean scope?
commandButton/commandLink/ajax action/listener method not invoked or input value not updated (point 5)
In a previous question BalusC gave me good advice on how a button, in place of a commandButton is useful for non ajax navigation. In particular it updates the destination address in the http: position which is useful for the user to bookmark a page.
I tried to use this information to my advantage until I came upon a problem. In a button I tried to use outcome="#{backing.something}" to find out that it gives me a null result. This looks like a timing problem in that action="#{}" is evaluated only when the button is pressed whereas outcome apparently wants a fixed string which gets checked when the page is loaded.
So I went back to commandButton with ajax="false". This has a problem that my navigation address is the page I came from, not the one I am navigating to. This is the wrong bookmark for the user.
I appreciate all the help I have received in stackoverflow on my learning exercise.
Ilan
The <h/p:button outcome> is not intented to invoke a bean action method, but to contain the outcome string directly. Any EL in there is evaluated immediately as a value expression. So the method behind it would immediately be invoked when you just open the page containing the <h/p:button>.
There are in your particular case basically two ways to invoke a bean action method on navigation. If you need to invoke it before the navigation takes place and the action isn't intented to be re-invoked everytime when the enduser reopens/reloads the GET request, then make it a POST-Redirect-GET request. It's a matter of adding faces-redirect=true to the outcome value in query string syntax.
E.g.
<p:commandButton action="#{bean.submit}" ... />
with
public String submit() {
// ...
return "nextpage?faces-redirect=true";
}
This way the browser will be redirected to the target page after POST, hence the enduser will see the target URL being reflected in the address bar.
Or if you need to invoke the action everytime when the enduser reopens/reloads the GET request, do the job in the (post)constructor or preRenderView listener method of the request/view scoped backing bean instead.
E.g.
<p:button outcome="nextpage" ... />
with
#ManagedBean
#RequestScoped
public class NextpageBacking {
public NextpageBacking() {
// In constructor.
}
#PostConstruct
public void onPostConstruct() {
// Or in postconstructor (will be invoked after construction AND injection).
}
public void onPreRenderView() {
// Or before rendering the view (will be invoked after all view params are set).
}
// ...
}
The pre render view listener method needs to be definied as follows in the nextpage
<f:event type="preRenderView" listener="#{nextpageBacking.onPreRenderView}" />
See also:
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
Communication in JSF 2.0 - Processing GET request parameters