1
votes

I am using Prime faces 3.4.1 as component framework and JSF 2.0 as the server side framework

Following is my requirement

1) I have a field with label as "Meeting Required". Then I have SelectOneRadio with two options "No" and "Yes" with default value as "No". I am using JSF/HTML component h:SelectOneRadio.

2) I have another field which is Calendar component and this is a primefaces calendar component. When the user selects "Yes" which indicates the "Meeting is required" and the user should select a date from the calendar control.

3) If the user selects "Yes" and does not select a date, then a Validation message should be displayed indicating that the date should be selected.

I created a Custom Validation component and attached to the SelectOneRadio and I am able to see the selected value in the Custom validator. Now, I try to get the value from the Calendar component to check if the value is empty, through UIComponent.getParent().findCompoent("rvmDate"), I get the component but I do not know how to check if the date component is empty or contain any values.

Please help me out to get the date value selected by the user.

Please help me out to resolve the issue. Or is there any other way? Please find the source code.

XHTML Page

<h:selectOneRadio id="rvmMeetingOption"
    readonly="#{wipMB.rvmMeetingOptionReadOnly}"
    value="#{wipMB.requirementsMeeting}" 
    disabled="#{wipMB.rvmMeetingOptionDisabled}"
    validator="#{wipMB.validateRVMDate}"
    validatorMessage="Please enter RVM Date>

     <f:selectItem itemLabel="No" itemValue="0"></f:selectItem>
     <f:selectItem itemLabel="Yes" itemValue="1" ></f:selectItem>
     <f:attribute value="#{rvmDateComp}" name="rvmDateComp"></f:attribute>
</h:selectOneRadio>

<p:calendar id="rvmDate" 
        readonly="#{wipMB.rvmMeetingDateReadOnly}"
        disabled="#{wipMB.rvmMeetingDateDisabled}"
        readonlyInput="true"
        navigator="true" mode="popup" 
        pattern="dd/MM/yyyy" 
        value="#{wipMB.rvmDate}" 
        effect="explode" 
        yearRange="1900:2500" 
        style="margin-left:5px"
        binding="#{rvmDateComp}"
</p:calendar>
<p:message id="rvmDateMsg" for="rvmDate" display="both" ></p:message>

Custom Validator

public void validateRVMDate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
{
    String invalidDate;
    String rvmOption;
    Date rvmDate;
    String rvmDt = "";

    try
    {

        FacesContext fc = FacesContext.getCurrentInstance();

        rvmOption = value.toString();
        DateFormat formatter = new SimpleDateFormat("E MMM dd HH:mm:ss Z yyyy");


        UIInput rvmCompDt = (UIInput)component.getAttributes().get("rvmDateComp");

        rvmDateId = rvmCompDt.getId();
        rvmDt = rvmCompDt.getSubmittedValue() == null ? "" : rvmCompDt.getSubmittedValue().toString();


        if (rvmOption.equals("1") && rvmDt.isEmpty())
        {

            FacesMessage msg = new FacesMessage("RVM date is required");
            msg.setSeverity(FacesMessage.SEVERITY_ERROR);
            fc.addMessage("rvmDateMsg", msg);
            throw new ValidatorException(msg);

        }
    }

    catch (Exception ex)
    {
        String msg = ex.getMessage();
    }
}
3

3 Answers

3
votes

In JSF each component is a little MVC stack of its own; there is the Model (stored as value), the Controller (the component object) and View (renderer). Validators and controllers are part of the architecture and are needed to move the values between the model and the view.

While JSF Validators play an important role, it is important only INSIDE this little MVC stack. They were not designed to "validate forms", they are made strictly to "validate component value". Lamentably, the name "validator" makes everyone who comes to JSF think, that each time any validating needs to be done, validator is the solution. Strangely, converters are not so abused.

In your case, building a custom validator created a strange situation, where:

  • validator and view have a cyclic dependency on each other,
  • there is a need to use hacks (such as "immediate") and low-level APIs,
  • hard-coding view logic in an unlikely place,
  • requires much more knowledge and is flaky. For example the logic of acquiring value from calendar might be different depending on whether radio button is before or after calendar in document order.

The problems above could be solved, but since they all arise from abusing JSF architecture, I think it would be better to rethink the problem. Since your validation concerns flow of application, it is a perfect fit for the action method, where all the complications will dissolve into a single, simple "if" statement with a conditional "addMessage".

3
votes

You first need to remove immediate="true" from the <p:calendar>, otherwise it's not processed at all when the radio button is processed.

Then, to check if a string is null or empty, just do

String dt = (String) uiCalendar.getSubmittedValue();

if (dt == null || dt.isEmpty()) {
    // dt is null or empty. Throw validator exception depending on the 
    // current radio button value. Note: you should not catch it yourself!
}

Note that this has nothing to do with JSF. It's just basic Java. Your initial attempt as if (dt == "") is indeed completely invalid. The String is an object, not a primitive. The == compares objects by reference, not by their internal value. Technically, you should have used if (dt.equals("")) instead, but the isEmpty() is nicer.


Unrelated to the concrete problem, a much easier way is to just check the radio button value in the required attribute of the calendar component. First bind the radio button component via binding to a variable in the view, then reference its UIInput#getValue() method in the required attribute.

<h:selectOneRadio id="rvmMeetingOption" binding="#{rvmMeetingOption}"
        readonly="#{wipMB.rvmMeetingOptionReadOnly}"
        value="#{wipMB.requirementsMeeting}" 
        disabled="#{wipMB.rvmMeetingOptionDisabled}">
    <f:selectItem itemLabel="No" itemValue="0"></f:selectItem>
    <f:selectItem itemLabel="Yes" itemValue="1" ></f:selectItem>
</h:selectOneRadio>

<p:calendar id="rvmDate" 
        readonly="#{wipMB.rvmMeetingDateReadOnly}"
        disabled="#{wipMB.rvmMeetingDateDisabled}"
        readonlyInput="true"
        navigator="true" mode="popup" 
        pattern="dd/MM/yyyy" 
        value="#{wipMB.rvmDate}" 
        effect="explode" 
        yearRange="1900:2500" 
        style="margin-left:5px"
        required="#{rvmMeetingOption.value == 1}">
</p:calendar>
0
votes

use

UIInput uiCalendar = (UIInput) component.getParent().findComponent("rvmDate");
Date test = uiCalendar.getValue();
if(test==null){
throw ValidatorException
}

test will then have the date filled in or will be null when nothing is chosen in teh date field