Ajax callback onstart works even if input is invalid - java

I have input fields and a command button inside an <h:form>. The input fields are required, and the validation error messages are showing well if something invalid is entered. At the end of the form, I have the following button:
<p:commandButton value="" ajax="true" update="foormm"
icon="ui-icon-check" actionListener="#{bean.sayHello}"
onstart="dialog.show()"
oncomplete="handleAjaxResponse(xhr, status, args)">
</p:commandButton>
The problem is, the dialog is showing regardless of validation. I thought the Ajax request was made iff inputs are validated. What's wrong here? Why does onstart get triggered even though the required fields are empty?
I use JSF 2.0, Primefaces 3.0 and Weblogic 12.1 with Eclipse.
Any help appreciated.

In other words, you want to show the dialog only when no validation errors have occurred after the postback? The onstart doesn't take that into account at all. It's invoked right before the ajax postback is invoked.
You'd need to approach it differently. The <p:dialog> has a visible attribute which can take an EL expression evaluating to a boolean outcome. You could make use of it:
<p:dialog id="dialog" visible="#{some condition}">
The #{some condition} can at its simplest be a combination of FacesContext#isPostback() and FacesContext#isValidationFalied(). So, if it's a postback and the validation has not failed, then the dialog should be visible.
<p:dialog id="dialog" visible="#{facesContext.postback and not facesContext.validationFailed}">
You just have to let your button update that dialog as well:
<p:commandButton value="" ajax="true" update="foormm dialog"
icon="ui-icon-check" actionListener="#{bean.sayHello}"
oncomplete="handleAjaxResponse(xhr, status, args)">
</p:commandButton>
Alternatively, you can also let the dialog's visible condition depend on some bean property which you set in the action(listener?) method:
<p:dialog id="dialog" visible="#{bean.saidHello}">

Related

onShow event fires unnecessarily during each page reloading

I have an issue with making a menu in my project using Primefaces. Actually, this menu will get me a possibility to show some small dialogs with settings for the workspace (by clicking on menu items). Each dialog should have data lazy loading from database. Unfortunately, when I include my dialogs in the page (single xhtml file or a couple xhtml files with ui:include), an event onShow happens on each page reload and this is wrong and provoke too many unnecessary requests to the database.
Here is an example:
UI part
<h:form id="form1">
<p:menubar id="mainMenu">
<p:submenu label="Main menu" icon="ui-icon-document">
<p:menuitem value="My settings" onclick="mySettingsWv.show()" url="#" />
</p:submenu>
</p:menubar>
</h:form>
<p:dialog id="mySettingsDlg" header="My Settings" widgetVar="mySettingsWv" resizable="false"
closable="true" modal="true" showEffect="fade" hideEffect="explode" dynamic="true"
closeOnEscape="true" onShow="#{mybean.onShow()}">
<h:outputLabel value="Settings dialog" />
</p:dialog>
ManagedBean part:
#ManagedBean (name = "mybean")
#ViewScoped
public class MyBean {
public static Logger log = Logger.getLogger(MyBean.class);
public void onShow() {
log.info("Method onShow is being called on each page reloading, but dialog still has not been shown");
}
}
If I use action "onclick" for <p:menuitem> for manual calling the necessary method, it still executes it for each page reloading. Also if I try use actionListener, action attributes it don't work. <p:ajax> cannot be attached to <p:menuitem>.
What should I do in that case? What is wrong can be in my code?
Have a look at the primefaces documentation for p:dialog (same problem with p:menuitem and onclick). There the documentation says about onShow:
Client side callback to execute when dialog is displayed. (emphasis added)
That means that you can specify a javascript function there, but it does not work that way to specify a action on your backingbean which is called everytime the dialog is shown. What happens in your case is the following: #{mybean.onShow()} is evaluated only when the file is parsed (i.e. the p:dialog is rendered into the HTML) and then the value which is returned by the method is inserted there (i.e. the empty String).
To fix this you have to define a javascript callback which makes the call on the bean. You can do this by using p:remoteCommand:
<p:remoteCommand name="onShow" action="#{mybean.onShow}"
partialSubmit="true" process="#this"/>
And then specify this callback as the onShow attribute:
<p:dialog id="mySettingsDlg" ...
onShow="onShow()">

How can I reset JSF UIInput components to their managed bean values

I want to reset JSF inputs to their original managed bean values after validation failed.
I have two forms inside the same page - the first form has a commandLink to initialize the second form. The second form is rendered as a dialog whose visibility is toggled through jQuery - for the purpose of this exercise, though, I can illustrate just with two forms on the same page. Also, while I'm using PrimeFaces 2.2.x in my app, the same behaviors appear with regular h:commandLink as well.
The issue I'm having is:
click link in first form to initialize second form
submit invalid values in second form
click link in first form again to initialize second form - invalid values still there and/or UIInput state is still invalid.
For example - take the following form
<h:form id="pageForm">
<h:commandLink actionListener="#{testBean.initialize}">Initialize, no execute
<f:ajax render=":dialogForm"/>
</h:commandLink>
<br/>
<h:commandLink actionListener="#{testBean.initialize}">Initialize, execute=#this
<f:ajax execute="#this" render=":dialogForm"/>
</h:commandLink>
</h:form>
<h:form id="dialogForm">
<h:messages/>
String property - Valid: <h:outputText value="#{property.valid}"/>
<br/>
<h:inputText id="property" binding="#{property}" value="#{testBean.property}">
<f:validateLength minimum="3"/>
</h:inputText>
<br />
Int property - Valid: <h:outputText value="#{intValue.valid}"/>
<h:inputText id="intValue" binding="#{intValue}" value="#{testBean.intValue}">
<f:validateLongRange maximum="50" />
</h:inputText>
<br/>
<h:commandLink actionListener="#{testBean.submit}">
Submit
<f:ajax render="#form" execute="#form"/>
</h:commandLink>
<h:commandLink actionListener="#{testBean.initialize}">Initialize, execute=#this
<f:ajax execute="#this" render="#form"/>
</h:commandLink>
</h:form>
Bean class:
#ManagedBean
#ViewScoped
public class TestBean {
private String property = "init";
private Integer intValue = 33;
// plus getters/setters
public void submit() { ... }
public void initialize() {
intValue = 33;
property = "init";
}
}
Behavior #1
click either "Initialize" link on the pageForm
inputs get initialized to "init", "33"
now submit something invalid for both fields like "aa", "99"
now click any of the "initialize" links again (they all seem to behave the same - makes no difference whether it's in the same form or different, or whether I have specified execute="#this" or not.)
Result => UIInput.isValid() = false, both values reset though ("init", "33").
Expected => valid = true (or is this not reasonable to expect?)
Behavior #2
click either "Initialize" link on the pageForm
inputs get initialized to "init", "33"
now submit something invalid for the text field but valid for the int field ("aa", "44")
now click any of the "initialize" links again
Result => "init", valid=false; 44, valid=true
Expected => "init", valid=true; 33, valid=true
I have also looked at:
JSF 2 - Bean Validation: validation failed -> empty values are replaced with last valid values from managed bean
and
How can I populate a text field using PrimeFaces AJAX after validation errors occur?
The suggestion to explicitly reset the state of UIInputs with resetValue does work, but I'm not happy with it.
Now, I sort of understand why the isValid is not resetting - my understanding of the JSF lifecycle is that once a value is submitted to a component, isValid is not reset until the component is successfully submitted and validated and the Update Model Values phase sets the bean value. So there may be no way around explicitly resetting the valid state in this case, since I want to use #{foo.valid} for conditional CSS styling.
What I don't understand, though, is why the components that successfully validated are not re-initializing from the bean. Perhaps my understanding of the JSF lifecycle is slightly off?
I understand the rules layed out in the answer to How can I populate a text field using PrimeFaces AJAX after validation errors occur? as they pertain to an individual component but not to the form as a whole - i.e., what happens if a component succeeds validation but the validation overall fails?
In fact, there may turn out to be no better way than explicitly calling resetValue on components. In my case, all of the dialogs are in the same big JSF view tree with the underlying page that opens them. So from JSF's perspective, the same view component state including invalid input values should be preserved until we navigate away from the view, as it has no visibility into how we're toggling display attributes client-side.
The only other thing that might work is if the components that make up the dialog are actually not rendered in the JSF view tree unless they're visible. In my case, they're always rendered, using CSS to toggle visibility.

Conditionally rendered button, design, calling an action

JFS1.2 + Richfaces 3.3
Situation is as follows:
JSP page renders conditionally one or another panelGroup.
Within each panelGroup there are couple setters and one command button.
Each of two panelGroups uses own bean for setting and performing action.
On the top of a page there's selectOneRadio with (obvious) two items - coresponding tow options of conditional rendering.
Page renders properly, switcher causes to render appropriate panel.
Case is, commands buttons doesn't call an action.
I know what's going on - when I click a button to call action dom is regenerated, but the value that hold my decision to display particular panel doesn't exist anymore. The button is not recreated, action is not fired.
Technically:
<h:selectOneRadio value="#{reportType}">
<f:selectItem itemLabel="x" itemValue="x"/>
<f:selectItem itemLabel="y" itemValue="y"/>
<a4j:support event="onclick" reRender="xPanel, yPanel/>
</h:selectOneRadio>
<h:panelGrid id="xPanel "columns="2" rendered="#{reportType eq 'x'}">
<...some setters>
<... commandbutton>
</h:panelGrid>
<h:panelGrid id="yPanel "columns="2" rendered="#{reportType eq 'y'}">
<...some setters>
<... commandbutton>
</h:panelGrid>
Question is, how to design the page to obtain proper rendering and actions?
For now, I created additional session bean that holds switching value (x|y), but that desing smells bad for me...
RichFaces 3.3 offers the <a4j:keepAlive> tag which does basically the same as Tomahawk's <t:saveState> and JSF2 #ViewScoped. Add the following line somewhere in your view:
<a4j:keepAlive beanName="#{bean}" />
This will keep the bean alive as long as you're returning null or void from action(listener) methods.
See also:
JSF 1.2: How to keep request scoped managed bean alive across postbacks on same view?

How to solve "p:message not working" in Primefaces?

I'm building a form that let the user enter database connection parameters. Once the parameters are typed
the user can test (btnTester) if connection can be established with its parameters.
In all cases, a message is produced for the user. Here the example of a failed connection attempt from backing bean code :
(...)
addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Connection failed.", t.getLocalizedMessage()));
(...)
Here is the form code. I'd like the message to appear in a p:message. Unfortunately, nothing happen. No message (connection successful or not) is displayed after the button :\
Even with the global attribute set to false or true.
<h:form prependId="false">
(...)
<h:panelGrid>
<!-- Other form components here -->
<f:facet name="footer">
<p:commandButton
id="btnTester"
value="Tester"
actionListener="#{assistantCreationSourcesBean.testerConnexionBase}"
update="msgTester" />
<p:message id="msgTester" for="btnTester" />
</f:facet>
</h:panelGrid>
</h:form>
What am I missing ?
You are missing an update attribute on your <p:commandButton> that specifies the ID of the <p:message> component to update.
You should give the message component and ID and specify it in update of the commandButton.
I think your problem is here:
addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Connection failed.", t.getLocalizedMessage()));
I'm assuming that you're calling FacesContext.addMessage(). That first parameter is the component id string. If you set it to null, then you force the message to be a global message. The <p:message> component you have defined is set to for="btnTester" so it will only display messages with a component id that matches the id of your btnTester component.
Except from the Javadoc for addMessage():
Append a FacesMessage to the set of messages associated with the specified client identifier, if clientId is not null. If clientId is null, this FacesMessage is assumed to not be associated with any specific component instance.
Link to FacesMessage Javadoc for addMessage() method
I use a growl instead of a message for solving my problem.
Found a solution that works Perfectly with message in Prime face.
Thought Sharing with you even if u worked it out with growl
Here is the solution
The add addMessage has 2 parameters, a UIComponent Client Id and FacesMessage. The facesMessage shows or defines the message and Component Child Id shows where the component is.
Here you have missed the Component Part.
In-order to make it work you should bind the UIComponent with the bean class.
So that it can find the Component .
Now do as follows,
Define a UIComponent in bean class
Bind it with the corresponding command Button
and get the Id From the component in the bean Class
Your Code must be changed like this
> private UIComponent component;
(...)
addMessage(component.getClientId(), new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Connection failed.", t.getLocalizedMessage()));
(...)
Inside the xhtml file you must bind the UIComponent
<h:form prependId="false">
(...)
<h:panelGrid>
<!-- Other form components here -->
<f:facet name="footer">
<p:commandButton
id="btnTester"
value="Tester"
actionListener="#{assistantCreationSourcesBean.testerConnexionBase}"
update="msgTester"
binding="#{assistantCreationSourcesBean.component}"/>
<p:message id="msgTester" for="btnTester" />
</f:facet>
</h:panelGrid>
</h:form>
Now the message will work..!! For me it worked perfectly :)
Here "for" attribute won't work for p:message use p:messages instead.
<p:messages id="txtname" showIcon="false"
showDetail="true" showSummary="false"
redisplay="false" for="someKey" display="text"/>
My guess is that you try to update a component that not yet being render. Try this
Use Firefox Firebug and check the html source to see if component id msgTester exist. If it is not then it explain why your update wont work. You cant update something that it is not there. How to fix this, well you can create a wrapper for this, and update your wrapper. Your wrapper should be something that it is always there, like h:form. IF you dont want to update the whole form, then you can try <p:outputPanel id="wrapper">. so your code would look like this
<h:form>
...
<p:outputPanel id="wrapper">
<p:commandButton ... update="wrapper"/>
<p:message />
</p:outputPanel>
</h:form>
try this and let me know if it work.
I have encountered this problem recently with PrimeFaces 6.0.19.
It turns out that I must have to call FacesContext.getCurrentInstance().validationFailed() before I call the addMessage() method so the message can be properly shown.
i.e.
FacesContext.getCurrentInstance().validationFailed();
//if you set the client ID to be null, you should add globalOnly=true in <p:messages>
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Connection failed.", t.getLocalizedMessage()));

Is there a way to not sending a whole web form when click a button?

I will try to be as brief as possible, please stay with me here
"A.jsf" -> managed bean : bean
"#{bean.list}": will take us to B.jsf
<p:growl id="msgs" showDetail="true"/>
<h:form id="myform1" enctype="multipart/form-data">
<p:panel header="Upload" style="font-size: 11px;">
<h:panelGrid columns="2" cellpadding="10">
<h:outputLabel value="Drawing:" />
<p:fileUpload fileUploadListener="#{bean.handleFileUpload}" update="msgs" allowTypes="*.*;"/>
</h:panelGrid>
<p:commandButton ajax="false" immediate="true" id="back" value="Back" action="#{bean.list}"/>
<p:commandButton ajax="false" id="persist" value="Persist" action="#{bean.handleRevision}" />
</p:panel>
</h:form>
Then the handleFileUpload()
if(!upload){
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "You do not have permission to upload.");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
...
"B.jsf" -> managed bean: bean2
...
<p:growl id="msgs" showDetail="true"/>
...
When I click upload, it give me a growl error message "You do not have permission to upload.", which is good. But then when I click "Back", which will take me to B.jsf, I see the growl message "You do not have permission to upload." again. What seem to be happening is as I click the "Back", I send other form request to upload, which then generated the same error message, which then being displayed at B.jsf. Is there a way to fix this, beside putting the "Back" button into an empty form, because now I have two buttons standing on top of each others, instead of side by side. I try to do this:
FacesContext.getCurrentInstance().addMessage("tom", msg);
hoping that it would send to component with id="tom", so then the growl with id=msgs, would not get load, but no luck. I try to turn the upload flag on when I click the Back button, but the web form get requested before the method that handle the back navigation get called.
It is not as brief as I want it to be, therefore I want apologize for it :D
beside putting the "Back" button into an empty form, because now I have two buttons standing on top of each others
The HTML <form> is by default a block element. HTML block elements are by default been placed in a new line. You actually want to make it an inline element. You can do this using display: inline; in CSS.
Back to the actual problem, it however surprises me that the fileUploadListener method is called in spite of the immediate="true" in the p:commandButton. I tried to reproduce this and I can confirm this. But I wouldn't expect it to happen. Normally the immediate="true" on a button is the solution to skip submitting of the "whole" form (at least, skip the UIInput components without this attribute). Further investigation learnt me that the p:fileUpload isn't an UIInput component at all and that the listener is fired during apply request values phase instead of validations or update model values phase. So this behaviour is fully predictable, but imo still an oversight in the design.
Since the p:fileUpload requires ajax="false" on the p:commandButton component, you can on the other hand also just remove it from the back button so that it fires an ajaxical request and hereby skips the fileUploadListener being called.
Actually, putting the button in a different form sounds like an excellent solution. The reason the buttons don't align any more is that the new starting <form> element starts on its own line. You should be able to prevent this by adding form { display: inline; } to your CSS file.
That said, if you have some leftover error messages that you want to get rid of, you can do this in the initializing method of your backing bean (if you have one). The following works peachily:
public void clearErrorMessages() {
//it may get messy to debug why messages are swallowed
logger.debug("clearing messages, coming from " + new Exception().getStackTrace()[1]);
Iterator iter = FacesContext.getCurrentInstance().getMessages();
while (iter.hasNext()) {
FacesMessage msg = (FacesMessage) iter.next();
logger.debug("clearing message: " + msg.getDetail());
iter.remove();
}
}
The disadvantage here is that any errors that occur between submitting the form and initializing the backing bean of the target page are also swallowed.

Categories