Search code examples
jsfjsf-2primefacesfacelets

How to handle in JS an error message thrown by a commandButton's action


In a JSF 2.1 wep app, I have a column of a dataTable that contains a commandButton. The action triggered by the commandButton may throw an error. I want to open a dialog in case no errors are thrown, or a box containing the error message in the other case.

JSF

<h:form>
    <p:dataTable var="foo" value="#{myBean.foos}" >
        <p:column>#{foo.id}</p:column>
        <p:column>
            <p:commandButton title="Action" action="#{myBean.action()}" onComplete="handleResult(xhr, status, args)" >
                <f:setPropertyActionListener value="#{foo}" target="#{myBean.selectedFoo}" />
            </p:commandButton>
        </p:column>
    </p:dataTable>
</h:form>

JavaScript

function handleRequest(xhr, status, args) {
    if (!args.success) {
        displayBox(args.errorMsg);
    } else {
        displayDialog();
    }
}

MyBean

@ManagedBean
@ViewScoped
public class MyBean {
    private List<Foo> foos;
    private Foo selectedFoo;

    public void action() {
        try {
            // business logic
        } catch (Exception ex) {
            RequestContext rContext = RequestContext.getCurrentInstance();
            rContext.addCallbackParam("success", false);
            rContext.addCallbackParam("errorMsg", ex.getMessage());
        }
    }
}

The only solution I can think of is using PrimeFaces' RequestContext.callBackParam() and writing a JS function that reads it and decides what to open.

Is there a smoother way? I'd consider to leverage the concept of validation, if possible, but I can't find an example of how to intercept the error in JS.


Solution

  • The canonical way is to just add a global faces message (thus, with null client ID).

    public void action() {
        try {
            // business logic
        } catch (Exception ex) {
            context.validationFailed();
            context.addMessage(null, new FacesMessage(
                FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
        }
    }
    

    (calling FacesContext#validationFailed() will cause #{facesContext.validationFailed} in EL to evaluate true; this is normally set in case of ValidatorException)

    Then, to display a box for global message only, just use <p:messages globalOnly="true">.

    <p:messages globalOnly="true" autoUpdate="true" />
    

    And to display a dialog on successful outcome, let it render when a successful postback is performed:

    <p:outputPanel autoUpdate="true">
        <p:dialog visible="true" rendered="#{facesContext.postback and not facesContext.validationFailed}">
            A successful postback is performed!
        </p:dialog>
    </p:outputPanel>
    

    Replace if necessary autoUpdate="true" of one or the both components by update="messagesId dialogPanelId" so that it's only triggered when the particular button is pressed.