0
votes

I have an action group on click of a button. First action, does some validation, second action throws up a confirm (Do you want to save?) And if yes, the third action goes off and does some other stuff.

The issue I have, is if validation fails on the first action, I don't want the other 2 actions to run, so I should get no confirmation etc.

If validation fails, I've tried doing a break, and a return false, but neither seem to work. I'm sure I'm missing something obvious, and suffering from Monday syndrome, but I can't seem to work it out!

Event handler code below, thanks:

        <xp:eventHandler event="onclick" submit="true"
        refreshMode="complete">
        <xp:this.action>
            <xp:actionGroup>

                <xp:executeScript>
                    <xp:this.script><![CDATA[#{javascript:for (var i = 1; i < viewScope.rows+1; i++) {
print("Starting Array.....");

var fieldName:string = "ObjectiveSelfAssessment" +i;
var fieldName2:string = "ObjectiveDetails" +i;
print ("Field Name: " + fieldName);

var fieldValue = document1.getItemValueString(fieldName);   
var fieldValue2 = document1.getItemValueString(fieldName2); 

print ("Field Value: " + fieldValue);
if (fieldValue =="" || fieldValue==null){
    print("Assessment Empty");
    if(!fieldValue2 =="" || !fieldValue2 == null){
        print("Objective Empty");
        //Do validation
        var o = {};
        o.title = "Validation Failed";
        o.body = "You must enter self assessment details for each objective";
        o.alertIcon = "fa-thumbs-down fa-lg";
        o.autoClose = true;
        o.alertType = "danger";
        requestScope.put("alertServer",o);
        //requestScope.put("validated",false);
        return false;
        break;
    }           
}
}
}]]></xp:this.script>
                </xp:executeScript>
                <xp:confirm>
                    <xp:this.message><![CDATA[#{javascript:"Are you sure you want to submit your self assessment?"}]]></xp:this.message>
                </xp:confirm>

                <xp:executeScript>
                    <xp:this.script><![CDATA[#{javascript:document1.replaceItemValue("rows",viewScope.rows);
//document1.replaceItemValue("status","Self Assessment Completed");
document1.save();
var o = {};
o.title = "Document Saved";
o.body = "This document has been succesfully submitted";
o.alertIcon = "fa-thumbs-up fa-lg";
o.autoClose = true;
o.alertType = "success";
requestScope.put("alertServer",o);
}]]></xp:this.script>
                </xp:executeScript>
            </xp:actionGroup>
        </xp:this.action>
    </xp:eventHandler>

Update 1: Conditional code on action group:

<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
    <xp:this.action>
        <xp:actionGroup>
            <xp:this.condition><![CDATA[#{javascript:for (var i = 1; i < viewScope.rows+1; i++) {
print("Starting Array.....");

var fieldName:string = "ObjectiveSelfAssessment" +i;
var fieldName2:string = "ObjectiveDetails" +i;
print ("Field Name: " + fieldName);

var fieldValue = document1.getItemValueString(fieldName);   
var fieldValue2 = document1.getItemValueString(fieldName2); 

print ("Field Value: " + fieldValue);
if (fieldValue =="" || fieldValue==null){
    print("Assessment Empty");
    if(!fieldValue2 =="" || !fieldValue2 == null){
        print("Objective Empty");
        var o = {};
        o.title = "Validation Failed";
        o.body = "You must enter self assessment details for each objective";
        o.alertIcon = "fa-thumbs-down fa-lg";
        o.autoClose = true;
        o.alertType = "danger";
        requestScope.put("alertServer",o);
        print("FALSE");
        return false;
        break;
    }else{
        print("TRUE");
        return true;
    }               
  }
}
}]]></xp:this.condition>

            <xp:confirm>
                <xp:this.message><![CDATA[#{javascript:"Test11111"}]]></xp:this.message>
            </xp:confirm>
            <xp:executeScript>
                <xp:this.script><![CDATA[#{javascript:document1.replaceItemValue("rows",viewScope.rows);
//document1.replaceItemValue("status","Self Assessment Completed");
document1.save();
var o = {};
o.title = "Document Saved";
o.body = "This document has been succesfully submitted";
o.alertIcon = "fa-thumbs-up fa-lg";
o.autoClose = true;
o.alertType = "success";
requestScope.put("alertServer",o);}]]></xp:this.script>
            </xp:executeScript>
        </xp:actionGroup>
    </xp:this.action></xp:eventHandler>
4

4 Answers

1
votes

Paul, I've accepted your answer reg doing more manual coding as that's the approach I've taken. I'm submitting an answer so I can show my solution using code formatting....

I now do my validation client side - The reason for getting the children etc, is I create/remove fields dynamically which are shown with repeat controls for my text box's, so I do not know the element id's....

var objcolparent = document.getElementById("ObjColOuter").children[0];
var sacolparent = document.getElementById("SAColOuter").children[0];
var rows = sacolparent.getElementsByTagName("TEXTAREA").length;

for (var i = 0; i < rows; i++) {
var saValue = sacolparent.getElementsByTagName("TEXTAREA")[i].value;
if(saValue ==""){
    var objValue = objcolparent.getElementsByTagName("TEXTAREA")[i].value;
    if(!objValue==""){
        // Validation failed, do client side bootalert
        var o = {};
        o.title = "Validation Failed";
        o.body = "You must enter self assessment details for each objective";
        o.alertIcon = "fa-thumbs-down fa-lg";
        o.alertType = "danger";
        bootAlert.show('alertServer',JSON.stringify(o))
        return false;
        break;
    }
}
}

if(confirm("Are you sure you want to submit your self assessment?")){
  return true;
}else{
  return false;
}

If validation is successful, it then continues to run my server side stuff:

document1.replaceItemValue("rows",viewScope.rows);
document1.replaceItemValue("status","Self Assessment Completed");
document1.save();
var o = {}
o.title = "Document Saved";
o.body = "This document has been succesfully submitted";
o.alertIcon = "fa-thumbs-up fa-lg";
o.autoClose = true;
o.alertType = "success";
requestScope.put("alertServer",o)

Thanks to everyone who contributed!

0
votes

You can add a condition to your actionGroup. Here's a simple example:

<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
    <xp:this.action>
        <xp:actionGroup condition="#{javascript:myCondition()}">
            xp:confirm>
                <xp:this.message><![CDATA[#{javascript:"Are you sure you want to submit your self assessment?"}]]></xp:this.message>
            </xp:confirm>
            <xp:executeScript>
                ...
            </xp:executeScript>
        </xp:actionGroup>
    </xp:this.action>
</xp:eventHandler>

So in your case you just add your first action as a condition and have it return true or false depending on whether the logic in the action group must run or not.

0
votes

Action groups are useful for basic, minimum-coding actions. But, as you're seeing, "easier" point-and-click, configuration-driven actions mean flexibility is harder. Manual coding may seem harder, but makes flexibility easier. If you put a confirm action on an XPage, preview it, and look at the source, it will tell you what function you need to use for manual coding. When I looked at the XSP class about seven years ago, I think the function used was XSP.confirm() equivalent to a basic JavaScript confirm() call. Using that may make your code more readable and make flexibility easier.

0
votes

There is a solution but it involves implementing a series of elements to properly support this scenario. But when it's up I believe it can be very helpful.

Your question is somewhat connected to a blog post of mine. To help you in your specific case I will try to summarize here all the moving parts. Hope you like reading...

1st element: a server-side helper method

If you do validation outside of the validation phase there's no real way to let the client know something went wrong, or better there might be but it would be disconnected from an eventual flow you want to stay in (your case). What you want to do first, therefore, is to create a way to tell the client. First thing we build a static class with a simple helper method:

public enum Helper {
    ;

    public static void setErrorHeader(HttpServletResponse response, PhaseId phaseId) {
        response.setHeader("Application-Error", phaseId.toString());
    }
}

We will later invoke this method to "flag" the validation as failed

2nd element: a client-side helper method

We need a JavaScript function that helps us tap into the response header we eventually add (add this function to your XPages through a JS library).

var helper = {
    isBadRequest : function(xhr) {
        return xhr.getResponseHeader("Application-Error") !== null;
    }
}

3rd element: a beefed up event handler

To get things started in the proper way we must leverage the event handler parameters:

<xp:button id="myButton" value="Three-step action">
    <xp:eventHandler
        id="myEventHandlerId"
        event="onclick"
        submit="false"
        action="#{myBean.makeItFlow}"
        script="threeStep('validate', 'threeStepSave(arguments[1].xhr)')"/>
</xp:button>

It's very important for the eventHandler to have the attributes defined as written above:

  • id: used to grab the event handler later on
  • submit="false": doesn't submit the form, we will do it manually
  • action: contains the reference to the server side bean method that will be invoked. I used the name myBean.makeItFlow. I'm making the assumption you know how to use managed beans
  • script: where the "real" action takes place

At this point in the script attribute we call the XPages client-side javascript method that does the post - XSP.partialRefreshPost() - and we pass the various parameters manually. In this case I chose to avoid putting all the necessary script in-line with the event. At the bottom of the page you can add the following helper functions:

<xp:scriptBlock
    value="
    function threeStep(param, onCompleteFunc) {
        var execId = '#{id:containerId}';
        var refreshId = '#{id:containerId}';
        var eventHandlerId = '#{id:myEventHandlerId}';

        var opts = {
            execId: execId,
            params: { '$$xspsubmitid': eventHandlerId, action: param },
            onError : 'console.log(arguments[0])'
        };

        if (onCompleteFunc) {
            opts.onComplete = onCompleteFunc;
        }

        XSP.partialRefreshPost(refreshId, opts);
    }

    function threeStepSave(xhr) {
        if (helper.isBadRequest(xhr)) {
            return alert('Validation failed');
        }

        if (!confirm('Do you want to continue?')) return;

        threeStep('save');
    }" />

The method's first parameter is the id that will be refreshed. Then we have an object whose properties define:

  • param: a parameter we want to pass in order to tweak the server-side method behaviour
  • onCompleteFunc: the client side javascript that will be invoked at the end of the server side method evaluation (the js must be a string and will be evaluated)
  • var execId: the block (and therefore scope) that will be submitted with the POST
  • var refreshId: the block that will be refreshed
  • var eventHandlerId: the very important reference to the event handler ID

4th element: the bean action method

Now, by means of a Java bean we define all the action logic behind the button. I decided to go with 1 specific method that will react to the action parameter value passed by the event handler.

public void makeItFlow() {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    String action = (String) externalContext.getRequestParameterMap().get("action");

    if ("validate".equals(action)) {
        // Your logic
        boolean failed = false;

        // When validation fails add the error response header
        if (failed) {
            HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
            Helper.setErrorHeader(response, PhaseId.INVOKE_APPLICATION);
        }
    } else if ("save".equals(action)) {
        // Let's save this thing
    }
}

I am adding a brief video of how it should behave (in the example I added some additional code to make the various phases obvious)

https://youtu.be/JayzcGex-rQ