0
votes

On my xpage I have defined a validator for an inputtext control:

<xp:inputText id="cv"
    disabled="#{!employeeBean.employee.editable}"
    value="1"
    validator="#{employeeValidator.valAttachments}">
    <xp:this.required><![CDATA[#{javascript:return submittedBy("btnSendToHR")}]]></xp:this.required>
    <xp:this.validators>
        <xp:validateRequired
            message="No CV added">
        </xp:validateRequired>
    </xp:this.validators>
</xp:inputText>

The validator is very basic and looks a bit as followed:

public void valAttachments(FacesContext facesContext, UIComponent component, Object value) {
    // my business logic here
    String msg = null;
    msg = "//collecting string from properties file";
    FacesMessage message = new FacesMessage(msg);
    throw new ValidatorException(message);  
}

Now I am trying to make my validator conditional to the button that has triggered the submit (submittedBy("btnSendToHR")) (inspiration I found in this blog-post http://dontpanic82.blogspot.com/2010/03/xpages-making-validation-behave.html):

<xp:this.validator><![CDATA[#{javascript://emploteeValidator.valAttachments
if (true == submittedBy("btnSendToHR")){
    importPackage(se.bank.app.test);
    var mv = new se.bank.app.test.EmployeeValidator();
    mv.valAttachments(facesContext, getComponent("cv"),"0" );
}}]]></xp:this.validator>

I see that the method is called (e.g. when I include a System.out.println() in the method) . An example:

Error while executing JavaScript action expression Script interpreter error, line=5, col=12: Error calling method

'valAttachments(com.ibm.xsp.domino.context.DominoFacesContext, com.ibm.xsp.component.xp.XspInputText, string)' on java class 'se.bank.app.test.EmployeeValidator' from valAttachments: test validation cv, submitted value=0

Can someone explain how I could make the validator depending on the submitting Id (a button) ?

2

2 Answers

0
votes

Maybe you're trying something more complicated than you need. The validator already has access to the component triggering the validation. See UIComponent component is one of the parameters. That may be the xp:eventHandler but getParent() navigates up from there. That may allow you to get all you need.

If you want to avoid coding in the validator, this.getParent() gets the component in eventHandler's SSJS. You could, for example, pass a parameter to set valid based on the ID.

0
votes

I think the problem is twofold: there's a required validator and a validator for the submitted value. If that is the case you need to take care of both.

XPages cheats with the required validator in the sense that it attaches it dynamically in the render phase. This is because there's no validator firing in case the input value is empty for the JSF framework XPages extends from. That means that if you have a validator but you don't have required=true on the input the validator won't run if the input value is empty.

As far as I know a custom required validator can't be used without using ugly workarounds. But as you tried to do you can play with flipping the required= value since it will be evaluated both in the GET and POST request.

With that being said, although I'm not a fan of what I'm going to say - I would probably ask myself why I want a UX/UI done in this way, you can actually bind the buttons and work more easily with the ids. Consider the following:

<xp:div id="containerForm">
    <xp:inputText id="inputText1" value="#{viewScope.whatever}"
        required="#{javascript:param['$$xspsubmitid'] === getClientId(eventSubmit.id)}" />

    <xp:button id="buttonSubmitValidate" value="Submit">
        <xp:eventHandler id="eventSubmit" binding="#{eventSubmit}" event="onclick"
            submit="true" execMode="partial" execId="containerForm" refreshMode="partial"
            refreshId="messages" />
    </xp:button>

    <xp:button id="buttonSubmitNoValidate" value="Submit No Validate">
        <xp:eventHandler event="onclick" submit="true"
            execMode="partial" execId="containerForm" refreshMode="partial"
            refreshId="messages" />
    </xp:button>
</xp:div>

<xp:div id="messages">
    <xp:messages globalOnly="false" />
</xp:div>

Here I use a couple of things inherited from the SSJS concept (sadly, but just a tad though). In the required property I retrieve the eventHandler client id by creating a handle to it through the binding property. In other words that event handler component will be available under the eventSubmit variable name. But eventSubmit.id will return the server-side component id, which we cannot evaluate upon because the $$xspsubmitid will contain its client-side counterpart. To get the client id for the component we then need to call getClientId(eventSubmit.id). At this point I have all I need to evaluate properly whether I want to require the field or not by comparing such value with what was passed in the POST request parameter $$xspsubmitid.

The other button's event handler will certainly have a different dynamically generated client id and therefore if you submit the form by clicking on the button it's attached to it won't fire the required validator. This is the quickest way to net your result.

However, if the configuration is more complex, say you have additional validators tied to the input then I would suggest something different:

<xp:inputText id="inputText1" value="#{viewScope.whatever}"
    required="#{javascript:validator.shouldBeFired(this)}" validator="#{validator.requireOtherCondition}">
    <xp:this.attrs>
        <xp:attr name="data-require-on-submit-id" value="#{id:eventSubmit}" />
    </xp:this.attrs>
</xp:inputText>

What I try to do here is to not repeat myself. I take advantage of a validator bean to hide away the evaluation of whether the event handler id is the one I'm looking for or not. Since I will still need that event handler id the validator method I handily - this is called hijacking, LOL - store it away as an input property.

At this point the required property and additional validator can rely on it. How?

First I just want to use a helper class for methods that can be used across my app - they might come handy elsewhere, who knows...

public enum Helper {
;

    public static Attr getComponentAttr(UIComponent component, String key) {
        if (component instanceof FacesAttrsObject) {
            FacesAttrsObject attrsObj = (FacesAttrsObject) component;
            List<Attr> attrs = attrsObj.getAttrs();

            if (attrs != null) {
                for (Attr attr : attrs) {
                    if (attr.getName().equals(key)) {
                        return attr;
                    }
                }
            }
        }

        return null;
    }

    @SuppressWarnings("unchecked")
    public static Map<String, String> getRequestParameterMap(FacesContext facesContext) {
        return (Map<String, String>) facesContext.getExternalContext().getRequestParameterMap();
    }

    public static String getSubmitId(FacesContext facesContext) {        
        return getRequestParameterMap(facesContext).get("$$xspsubmitid");
    }

}

I hope the above methods result pretty explanatory. At this point I write the validator bean methods:

public class ValidatorBean implements Serializable {

    private static final long serialVersionUID = 1L;

    public void requireOtherCondition(FacesContext facesContext, UIComponent component, Object value)
            throws ValidatorException {
        if (shouldBeFired(facesContext, component) && String.valueOf(value).length() < 3) {
            FacesMessage message = new FacesMessage("Hey, it's too short!");

            throw new ValidatorException(message);
        }
    }

    public boolean shouldBeFired(UIComponent component) {
        return shouldBeFired(FacesContext.getCurrentInstance(), component);
    }

    public boolean shouldBeFired(FacesContext facesContext, UIComponent component) {
        Attr attr = Helper.getComponentAttr(component, "data-require-on-submit-id");

        return attr != null && attr.getValue().equals(Helper.getSubmitId(facesContext));
    }

}

The shouldBeFired method will traverse the input attribute collection looking for an attribute named data-require-on-submit-id. If and when it founds it, it compares it with the current submitted id retried throught the Helper class method getSubmitId. Basically this is the same thing we did before directly on the xsp page with SSJS.

The requireOtherCondition method is just a dumb validator that will fire only if the same condition for the required attribute is met. If it doesn't there's no validator firing. If it does the validator is run - you can instantiate an existing one of just quickly do it there (in my case it fires if the input value length is minor than 3). See? It's as simple complicated as that.