Primefaces trigger onerror when exception occurs - jsf

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!

Related

Notify the user about the end of the session in JSF 2.3

JavaEE, JSF-2.3, Websocket, WebApplication, WildFly.
For each user, a session is created, within which it operates, authorization, authentication, and so on. After 15 minutes of inactivity, the session is automatically destroyed, thanks to the settings of web.xml -
<session-config>
<session-timeout>15</session-timeout>
</session-config>
In JSF-2.3 available WebSocket, so I decided to do so ExitBean.java -
#Inject
#Push(channel = "exit")
PushContext push;
#PreDestroy
public void sessionTimeOut() {
push.send("exitEvent");
}
On the page, respectively, exit.xhtml -
<h:form >
<f:websocket channel="exit" scope="session">
<f:ajax event="exitEvent" onevent="PF('dlg1').show()"/>
</f:websocket>
</h:form>
At the end of the session, judging by the logs, the sessionTimeOut() method works, it's still #PreDestroy, but there is no response on the page.
For the test, I placed a button on the exit.xhtml page, by clicking on which the sessionTimeOut() method is called. When this button is clicked, event - "exitEvent", executes as expected, invoking the PrimeFaces script PF('dlg1').show(), which displays a dialog box.
I suspect that websockets are killed even earlier than the #Predestroy method is called.
There is another option with the websocket, it looks like this:
<h:form >
<f:websocket channel="exit" scope="session" onclose="PF('dlg1').show()"/>
</h:form>
But it works only when the page loads and again no reaction to the end of the session.
Two questions:
How to handle the end of a session using websockets?
In extreme cases, offer an alternative.
Your technical problem is that you didn't specify a function reference in onevent or onclose attribute. It goes like:
onevent="function() { PF('dlg1').show() }"
onclose="function() { PF('dlg1').show() }"
or
onevent="functionName"
onclose="functionName"
where functionName is defined as a real function:
function functionName() {
PF('dlg1').show();
}
The correct approach is explained in Events section of javax.faces.Push javadoc:
<f:websocket channel="exit" scope="session"
onclose="function(code) { if (code == 1000) { PF('dlg1').show() }}" />
or
<f:websocket channel="exit" scope="session" onclose="exitListener" />
function exitListener(code) {
if (code == 1000) {
PF('dlg1').show();
}
}
See also:
Automatically perform action in client side when the session expires
Execute JavaScript before and after the f:ajax listener is invoked

How to refresh page after f:actionlistener event

Working with JSF, I have a <ux:confirm> tag, which has a confirm button. When clicked it triggers a actionListenerEvent. The page and the objects in the faces context are updated, however I have a bootstrap accordion which is not updated. A solution would be refreshing the page, which is my question.
<ux:confirm
ok="#{message.get('Label.Sim')}"
ajax="true"
render="form-consulta"
cancel="#{message.get('Label.Nao')}"
title="#{message.get('Label.Excluir')}"
message="#{message.get('Msg.DesejaExcluirRegistro')}"
>
<f:actionListener
for="onOkClick"
binding="#{bean.excluir()}"
/>
</ux:confirm>
Ok, solved by just adding a JavaScript function on the ajax event and also calling somewhat a template method using javascript.
<f:ajax
render="#{cc.attrs.render}"
disabled="#{not cc.attrs.ajax}"
onevent="onEventConfirm"
/>
function onEventConfirm(data) {
App.ajax.onEvent(App.view.block, null, null, App.view.unblock);
var status = data.status;
switch (status) {
case "complete":
if(shouldRefreshAfterConfirmation)
this.location.reload();
break;
}
}

Programmatically control which components should be ajax-updated

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" />

Error trying to execute method after error catch

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.

How to set the exception message in a <h:message>?

I am trying to set an exception message in the <h:message>.
Here is the relevant view code:
<h:inputText id="titleId" value="#{bookController.book.title}"/>
<h:message for="titleId"/>
<h:commandButton value="Create a book" actionListener="#{bookController.doCreateBook}" action="listBooks"/>
I need a message to be displayed when the titleId is empty. My #Stateless EJB method throws an exception when the title is empty:
public Book createBook(Book book) throws CustomException {
if(book.getTitle().isEmpty()) {
throw new CustomException("Please, type a Title !");
}
else {
em.persist(book);
return book;
}
}
My backing bean catches it and sets a message:
public void doCreateBook() {
FacesContext ctx = FacesContext.getCurrentInstance();
try {
book = bookEJB.createBook(book);
bookList = bookEJB.findBooks();
} catch (CustomException e) {
ctx.addMessage("titleId", new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", e.getMessage()));
}
}
What I except is, when the exception occurs, an error message must be displayed near the input text tag, but it isn't the case, the execution displays the page with list of books and the "Error" message displayed under the list, as shown below:
How can I get the full exception message to show up next to the input field?
Apart from the erroneous message handling which Thinksteep has already answered, your other mistake is that you're doing validation in an action method. This is not right. You should be using JSF builtin validation facilities instead. Whenever the JSF builtin validation fails, then the action method will not be invoked and the page will also not navigate. The enduser sticks to the current form and the message will appear in the therefor specified <h:message> tag.
In your particular case, you just need to set the required attribute.
<h:inputText id="titleId" value="#{bookController.book.title}" required="true" />
<h:message for="titleId" />
If you want to customize the default required message, use requiredMessage attribute.
<h:inputText id="titleId" value="#{bookController.book.title}"
required="true" requiredMessage="Please, type a Title !" />
<h:message for="titleId" />
Remove that input validation from the EJB method. It doesn't belong there. The EJB isn't responsible for that, the caller (which is in your case thus your JSF code) is responsible for that.
ctx.addMessage("titleId", new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", e.getMessage()));
Your message text is Error and you are getting same. Change "Error" here to what ever you want.
PUT <h:messages showDetail="true" />

Resources