72

Minimal example dialog:

<p:dialog header="Test Dialog"  
          widgetVar="testDialog"> 
  <h:form> 
    <p:inputText value="#{mbean.someValue}"/> 

    <p:commandButton value="Save" 
                     onsuccess="testDialog.hide()" 
                     actionListener="#{mbean.saveMethod}"/> 
  </h:form>       
</p:dialog> 

What I want to be able to do is have the mbean.saveMethod somehow prevent the dialog from closing if there was some problem and only output a message through growl. This is a case where a validator won't help because there's no way to tell if someValue is valid until a save is submitted to a back end server. Currently I do this using the visible attribute and point it to a boolean field in mbean. That works but it makes the user interface slower because popping up or down the dialog requires hitting the server.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
JOTN
  • 6,120
  • 2
  • 26
  • 31

6 Answers6

165

The onsuccess runs if ajax request itself was successful (i.e. there's no network error, uncaught exception, etc), not if action method was successfully invoked.

Given a <p:dialog widgetVar="yourWidgetVarName">, you could remove the onsuccess and replace it by PrimeFaces RequestContext#execute() inside saveMethod():

if (success) {
    RequestContext.getCurrentInstance().execute("PF('yourWidgetVarName').hide()");
}

Note: PF() was introduced in PrimeFaces 4.0. In older PrimeFaces versions, you need yourWidgetVarName.hide() instead.

If you prefer to not clutter the controller with view-specific scripts, you could use oncomplete instead which offers an args object which has a boolean validationFailed property:

<p:commandButton ...
    oncomplete="if (args &amp;&amp; !args.validationFailed) PF('yourWidgetVarName').hide()" />

The if (args) check is necessary because it may be absent when an ajax error has occurred and thus cause a new JS error when you try to get validationFailed from it; the &amp; instead of & is mandatory for the reason explained in this answer, refactor if necessary to a JS function which you invoke like oncomplete="hideDialogOnSuccess(args, 'yourWidgetVarName')" as shown in Keep <p:dialog> open when validation has failed.

If there is however no validation error and the action method is successfully triggered, and you would still like to keep the dialog open because of e.g. an exception in the service method call, then you can manually trigger validationFailed to true from inside backing bean action method by explicitly invoking FacesContext#validationFailed(). E.g.

FacesContext.getCurrentInstance().validationFailed();
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 2
    Using RequestContext is pretty interesting. I didn't know you could do that. – JOTN Feb 09 '12 at 01:35
  • 1
    The expression in oncomplete is needs to be negated. oncomplete="if (args.validationFailed) ... " – Steven Shaw Jan 11 '13 at 01:00
  • 1
    @BalusC `oncomplete="if(args && !args.validationFailed)` this is my way of doing it. is `null` check in my code unneccessary? – Kerem Baydoğan Mar 11 '13 at 16:10
  • @BalusC It works except error messages. I have to update="@form", right? But it close the dialog even validation errors. – banterCZ Apr 19 '13 at 07:40
  • @banterCZ: apparently you aren't performing validation using normal JSF validators, but manually in e.g. action methods by manually adding faces messages. – BalusC Apr 19 '13 at 10:31
  • @BalusC No, it is just . But I have had bad order of `form` and `dialog` – banterCZ Apr 19 '13 at 10:41
  • @banterCZ: The dialog must have its own form, yes. Looking in the generated HTML DOM tree should clear that up. – BalusC Apr 19 '13 at 10:42
  • 1
    @BalusC, why using actionlistener instead of action in this case ? – Mahmoud Saleh Jun 27 '13 at 11:25
  • 2
    @Mah: I was just taking over OP's original code. The question wasn't about actionListener vs action, so I kept OP's original code as is. But I agree that this is not the recommended way, for the case you're wondering. – BalusC Jun 27 '13 at 11:35
  • @BalusC I've with this problem and I solve part of it with the `RequestContext` solution, thank you for that. Is there a way to update a component in the same way? I ask because I want to update one or other component depending if there is an error which I keep the dialog open therefore update the form in dialog or it is ok and I close the dialog and update the globalMessages – Jorge Campos Mar 30 '15 at 19:11
  • @Jorge: explore the methods of `RequestContext`. – BalusC Mar 31 '15 at 05:48
  • @BalusC I've already did it `RequestContext#update` Thank you so much!! – Jorge Campos Mar 31 '15 at 11:12
  • In PrimeFaces 6.2 this code is deprecated, use: `PrimeFaces.current().executeScript("PF('testDialog').hide()");` instead. – Rafi Nov 26 '18 at 13:58
15

I've just googled up this solution. Basically the idea is to use actionListener instead of button's action, and in backing bean you add callback parameter which will be then check in button's oncomplete method. Sample partial code:

JSF first:

<p:commandButton actionListener="#{myBean.doAction}"
   oncomplete="if (!args.validationFailed &amp;&amp; args.saved) schedulesDialog.hide();" />

Backing bean:

public void doAction(ActionEvent actionEvent) {
    // do your stuff here...
    if (ok) {
        RequestContext.getCurrentInstance().addCallbackParam("saved", true);
    } else {
        RequestContext.getCurrentInstance().addCallbackParam("saved", false);
    }
}

Hope this helps someone :)

Divyesh Kanzariya
  • 3,629
  • 3
  • 43
  • 44
soltysh
  • 1,464
  • 12
  • 23
15

Using the oncomplete attribute from your command button and really simple script will help you a lot.

Your dialog and command button would be something similar to this:

<p:dialog widgetVar="dialog">
   <h:form id="dialogView">
       <p:commandButton id="saveButton" icon="ui-icon-disk"
           value="#{ui['action.save']}"
           update=":dataList :dialogView"
           actionListener="#{mbean.save()}"
           oncomplete="handleDialogSubmit(xhr, status, args)" />
   </h:form>
 </p:dialog>

An the script would be something like this:

<script type="text/javascript">
    function handleDialogSubmit(xhr, status, args) {
        if (args.validationFailed) {
            dialog.show();
        } else {
            dialog.hide();
        }
    }
</script>
Alonso Dominguez
  • 7,750
  • 1
  • 27
  • 37
7

I use this solution:

JSF code:

<p:dialog ... widgetVar="dlgModify" ... >
...
<p:commandButton value="Save" update="@form" actionListener="#{AdminMB.saveTable}" />
<p:commandButton value="Cancel" oncomplete="PF('dlgModify').hide();"/>

Backing bean code:

public void saveTable() {
    RequestContext rc = RequestContext.getCurrentInstance();
    rc.execute("PF('dlgModify').hide()");
}
Antaeus
  • 71
  • 1
  • 1
4

The easiest solution is to not have any "widget.hide", neither in onclick, neither in oncomplete. Remove the hide functions and just put

visible="#{facesContext.validationFailed}" 

for the dialog tag

makkasi
  • 6,328
  • 4
  • 45
  • 60
3

I believe this is the cleanest solution. Doing this you don't need to change your buttons code. This solution overrides the hide function prototype.

$(document).ready(function() {
    PrimeFaces.widget.Dialog.prototype.originalHide = PrimeFaces.widget.Dialog.prototype.hide; // keep a reference to the original hide()
    PrimeFaces.widget.Dialog.prototype.hide = function() {
        var ajaxResponseArgs = arguments.callee.caller.arguments[2]; // accesses oncomplete arguments
        if (ajaxResponseArgs && ajaxResponseArgs.validationFailed) {
            return;  // on validation error, prevent closing
        }
        this.originalHide();
    };
});

This way, you can keep your code like:

<p:commandButton value="Save" oncomplete="videoDetalheDialogJS.hide();" 
   actionListener="#{videoBean.saveVideo(video)}" />
Luís Soares
  • 5,726
  • 4
  • 39
  • 66