for a university project we have to create a website using primefaces and spring boot.
My problem lies in the error handling. I have a p:dialog which lets you edit user information. Once you save the user, the dialog should close. However, if the save is not successful a error message should appear and the user-edit-dialog should stay open.
This is the commandButton which triggers the save:
<p:commandButton value="Save"
action="#{userDetailController.doSaveUser()}"
ajax="true"
validateClient="true"
oncomplete="if (args && args.closeForm) PF('userEditDialog').hide()"
update=":userForm:usersTable msgs mainPanel" />
As you can see it's an ajax request so for handling exception we use the integrated PrimeExceptionHandlerFactory.
<factory>
<exception-handler-factory>org.primefaces.application.exceptionhandler.PrimeExceptionHandlerFactory</exception-handler-factory>
</factory>
We are also displaying a dialog when a exception occurs.
<p:ajaxExceptionHandler type="at.qe.sepm.skeleton.utils.GeneralExpectedException" update="exceptionDialog"
onexception="PF('exceptionDialog').show();" />
<p:dialog id="exceptionDialog"
header="#{pfExceptionHandler.exception.type}"
widgetVar="exceptionDialog"
resizable="false">
Message: #{pfExceptionHandler.message}
<p:separator rendered="#{exceptionHelperBean.displayException(pfExceptionHandler.exception)}" />
<h:outputText rendered="#{exceptionHelperBean.displayException(pfExceptionHandler.exception)}"
value="#{pfExceptionHandler.formattedStackTrace}"
escape="false" />
</p:dialog>
My problem has to do with this line oncomplete="if (args && args.closeForm) PF('userEditDialog').hide()". The user-edit-dialog will only be closed when the closeForm flag is set. Which is done on the server:
PrimeFaces.current().ajax().addCallbackParam("closeForm", true);
From my understanding from other frameworks, when a exception occurs the server returns HTTP 500 and the onerror callback on the commandButton triggers.
Is there a more elegant way to do this? Ideally i don't want the oncomplete callback to trigger in the first place. I also tried using the onsuccess callback which doesn't get triggered at all, even if the request returns HTTP 200.
edit:
This is the doSaveUser methods inside the userDetailController:
public void doSaveUser() throws Exception {
user = this.userService.saveUser(user);
PrimeFaces.current().ajax().addCallbackParam("closeForm", true);
}
It internally calls the userServices save method where i manually throw the exception:
public User saveUser(User user) throws Exception {
if (user.isNew()) {
User existingUser = userRepository.findFirstByUsername(user.getUsername());
if(existingUser != null) {
throw new GeneralExpectedException("User with name " + user.getUsername() + " already exists",
ExceptionType.WARNING);
}
I hope this makes the problem more clear. Saving a user which already exists throws a exception - so the user edit dialog should stay open, in order to make adjustments. However, I also want the Exception dialog to trigger so you can see why saving is not possible.
Thank you for your help!
I'm creating a some sort of online shop with JSF. I have a product.xhtml page that displays the product by ID. I'm passing that ID as a param (.../product.xhtml?id=3) and I'm getting the ID with <ui:param name="productID" value="#{request.getParameter('id')}" />
That all works well.
Next, I'm showing and hiding certain elements in the page with <f:subview> (I've used <ui:fragment> before). The reason is that if the user deletes the ?id=3 ID parameter, the page will show an error (eg. code <f:subview id="main" rendered="#{productID != null and productID != ''}">). Another reason is that if the product belongs to the buyer, the BUY button will not appear and if the user is not authenticated the BUY button will not appear.
The problem is with the BUY button. It is in a form and the action of the button is just a simple test method (for now) from the CDI bean that prints something to the server console and redirects the user. Unfortunately, this does not work. The page (/product.xhtml) gets reloaded with no ID param.
I've tried several things like this and this and nothing is working.
I've tried using the ViewScoped and SessionScoped for my CDI bean instead of RequestScoped, but that does nothing. The ViewScoped fails to build.
I've also changed the <ui:fragment> to <f:subview>
Here's some code..
CDI bean controller
#Named
#RequestScoped
public class ProductManager {
...
public String buy(Product product) {
FacesContext context = FacesContext.getCurrentInstance();
try {
HttpSession session = Util.getSession();
User buyer = (User)session.getAttribute("user");
Date date = new Date();
System.out.println("TEST DATA: ");
System.out.println("sale product: "+product.getTitle());
System.out.println("sale buyer: "+buyer.getUsername());
System.out.println("sale date: "+date);
}
catch(Exception ex) {
System.err.println("ProductManager#buy -> "+ex.getMessage());
}
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "An error occured", null));
return "index";
}
...
}
Products.xhtml
...
<ui:param name="productID" value="#{request.getParameter('id')}" />
...
<f:subview id="buyBtn" rendered="#{user.username != login.username and login.regUser}">
<h:form style="margin-top: 30px;">
<b:navCommandLink styleClass="btn btn-info btn-block" disabled="#{!product.status}" value="Buy" action="#{productManager.buy(product)}"></b:navCommandLink>
</h:form>
</f:subview>
...
I can provide full code if needed.
What I'm expecting the code to do is that whenever I click on the BUY button, I'll get redirected to my page and the TEST DATA will be printed on server console.
After many. many attempts I solved this. I cannot find the exact explanation, as I read a ton of articles on this topic.
I used OmniFaces. Next in products.xhtml I changed h:form to o:form (from OmniFaces) and have these two set to true includeRequestParams="true" includeViewParams="true". My bean then became org.omnifaces.cdi.ViewScoped
This is also a great article
I have a complex form where the user fills a few fields, and has two options: generate a license file or save the changes. If the user clicks on the generate license file button without saving the changes, I render a small component with an error message asking him to save before generating the license.
To display the component with a warning message, I want to use ajax to avoid rendering the whole page just to render the warning component. Of course, if the changes were saved, then the warning message is not required and I redirect the user to another page.
I have a change listener on the changeable fields to detect when a change has been made. What I don't know is the conditional execution. The "render with ajax if unsaved OR redirect if saved" part. Here's the logic
if(saved){
redirect();
}else{
ajax.renderWarning()
}
--EDIT--
I'm going to add more info because I realized I'm leaving things too open ended.
Here's one example of an updateable field.
<h:inputText name="computername3" value="#{agreement.licenseServerBeans[2].computerId}" valueChangeListener="#{agreement.fieldChange}">
<rich:placeholder value="Add Computer ID"/>
</h:inputText>
The fieldChange() bean method
public void fieldChange(ValueChangeEvent event) {
change = true; //change is a boolean, obviously :P
}
Here's the generate license button jsf
<h:commandLink action="#{agreement.generateLicenseFile}">
<span class="pnx-btn-txt">
<h:outputText value="Generate License File" escape="false" />
</span>
</h:commandLink>
Here's the generateLicenseFile() method
public String generateLicenseFile(){
....//lots of logic stuff
return "/licenseGenerated.xhtml?faces-redirect=true";
}
Use PartialViewContext#getRenderIds() to get a mutable collection of client IDs which should be updated on the current ajax request (it's exactly the same as you'd specify in <f:ajax render>, but then in form of absolute client IDs without the : prefix):
if (saved) {
return "/licenseGenerated.xhtml?faces-redirect=true";
}
else {
FacesContext.getCurrentInstance().getPartialViewContext().getRenderIds().add("formId:messageId");
return null;
}
Returning null causes it to redisplay the same view. You can even add it as a global faces message and let the ajax command reference the <h:messages> in the render.
if (saved) {
return "/licenseGenerated.xhtml?faces-redirect=true";
}
else {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(...));
return null;
}
with
<h:messages id="messages" globalOnly="true" />
...
<f:ajax render="messages" />
I have this jsf code
<h:form>
<h:inputText value="#{agreement.serviceId}"/>
<h:commandButton value="Enter" action="#{agreement.build}" />
<h:form rendered="#{!agreement.valid}">
<h:outputText value="Service id not valid. Please try again"/>
</h:form>
<h:form>
This is the scoped bean's build method.
public String build(){
try{
...//lots of backend logic
valid = true;
return "/agreementDetail.xhtml?faces-redirect=true";
}catch(Exception e){
valid = false;
return null;
}
}
Basically, here's the behavior I need:
The user inputs a serviceId. If this service id is valid, it redirects the user to the agreementDetail.xhtml page. If false, the user remains in the main.xhtml page and the "Service id not valid..." message is rendered.
This is what's happening:
If the user inputs a correct service id, everything works fine. If the user returns to main.xhtml and inputs an incorrect service id, the error is displayed correctly. But now, if the user inputs a correct service id, the build() method is not executed. (I've confirmed this with logging).
Basically, once the user inputs a wrong value, the build() method won't be executed ever again unless the user signs off and signs in again. Clearly, something's going on when the build() finds an error and catches the exception.
Any ideas?
You are nesting forms in your code. That is not allowed in JSF/HTML. You should replace the inner form with a <h:panelGroup> and everything should be fine.
In my JSF/Facelets app, here's a simplified version of part of my form:
<h:form id="myform">
<h:inputSecret value="#{createNewPassword.newPassword1}" id="newPassword1" />
<h:message class="error" for="newPassword1" />
<h:inputSecret value="#{createNewPassword.newPassword2}" id="newPassword2" />
<h:message class="error" for="newPassword2" />
<h:commandButton value="Continue" action="#{createNewPassword.continueButton}" />
</h:form>
I'd like to be able to assign an error to a specific h:message tag based on something happening in the continueButton() method. Different errors need to be displayed for newPassword and newPassword2. A validator won't really work, because the method that will deliver results (from the DB) is run in the continueButton() method, and is too expensive to run twice.
I can't use the h:messages tag because the page has multiple places that I need to display different error messages. When I tried this, the page displayed duplicates of every message.
I tried this as a best guess, but no luck:
public Navigation continueButton() {
...
expensiveMethod();
if(...) {
FacesContext.getCurrentInstance().addMessage("newPassword", new FacesMessage("Error: Your password is NOT strong enough."));
}
}
What am I missing? Any help would be appreciated!
FacesContext.addMessage(String, FacesMessage) requires the component's clientId, not it's id. If you're wondering why, think about having a control as a child of a dataTable, stamping out different values with the same control for each row - it would be possible to have a different message printed for each row. The id is always the same; the clientId is unique per row.
So "myform:mybutton" is the correct value, but hard-coding this is ill-advised. A lookup would create less coupling between the view and the business logic and would be an approach that works in more restrictive environments like portlets.
<f:view>
<h:form>
<h:commandButton id="mybutton" value="click"
binding="#{showMessageAction.mybutton}"
action="#{showMessageAction.validatePassword}" />
<h:message for="mybutton" />
</h:form>
</f:view>
Managed bean logic:
/** Must be request scope for binding */
public class ShowMessageAction {
private UIComponent mybutton;
private boolean isOK = false;
public String validatePassword() {
if (isOK) {
return "ok";
}
else {
// invalid
FacesMessage message = new FacesMessage("Invalid password length");
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(mybutton.getClientId(context), message);
}
return null;
}
public void setMybutton(UIComponent mybutton) {
this.mybutton = mybutton;
}
public UIComponent getMybutton() {
return mybutton;
}
}
In case anyone was curious, I was able to figure this out based on all of your responses combined!
This is in the Facelet:
<h:form id="myform">
<h:inputSecret value="#{createNewPassword.newPassword1}" id="newPassword1" />
<h:message class="error" for="newPassword1" id="newPassword1Error" />
<h:inputSecret value="#{createNewPassword.newPassword2}" id="newPassword2" />
<h:message class="error" for="newPassword2" id="newPassword2Error" />
<h:commandButton value="Continue" action="#{createNewPassword.continueButton}" />
</h:form>
This is in the continueButton() method:
FacesContext.getCurrentInstance().addMessage("myForm:newPassword1", new FacesMessage(PASSWORDS_DONT_MATCH, PASSWORDS_DONT_MATCH));
And it works! Thanks for the help!
You also have to include the FormID in your call to addMessage().
FacesContext.getCurrentInstance().addMessage("myform:newPassword1", new FacesMessage("Error: Your password is NOT strong enough."));
This should do the trick.
Regards.
Remember that:
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage( null, new FacesMessage( "The message to display in client" ));
is also valid, because when null is specified as first parameter, it is applied to the whole form.
More info: coreservlets.com //Outdated
JSF is a beast. I may be missing something, but I used to solve similar problems by saving the desired message to a property of the bean, and then displaying the property via an outputText:
<h:outputText
value="#{CreateNewPasswordBean.errorMessage}"
render="#{CreateNewPasswordBean.errorMessage != null}" />
Found this while Googling. The second post makes a point about the different phases of JSF, which might be causing your error message to become lost. Also, try null in place of "newPassword" because you do not have any object with the id newPassword.
I tried this as a best guess, but no luck:
It looks right to me. Have you tried setting a message severity explicitly? Also I believe the ID needs to be the same as that of a component (i.e., you'd need to use newPassword1 or newPassword2, if those are your IDs, and not newPassword as you had in the example).
FacesContext.getCurrentInstance().addMessage("newPassword1",
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error Message"));
Then use <h:message for="newPassword1" /> to display the error message on the JSF page.
Simple answer, if you don't need to bind it to a specific component...
Java:
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Authentication failed", null);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
XHTML:
<h:messages></h:messages>