3
votes

I've a form in JSF 2 where I'm using a double field to specify a range of dates. The aim of that is not to let the user to pick a first date which is before the second one. So I want to perform a validation before the form is sent, using p:calendar components. What I do is to tie a validator to the second calendar input in order to access the first component internally and compare dates.

Here it is my xhtml page:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:p="http://primefaces.org/ui">

<h:head />
<h:body>
    <h:form id="date_form">
        <p:calendar id="date1input" value="#{dateTestBean.date1}" />
        <p:calendar value="#{dateTestBean.date2}" validator="dateValidator">
            <f:attribute name="date1" value="#{date1input}" />
        </p:calendar>
        <p:commandButton value="Submit" action="#{dateTestBean.submit}"
            ajax="false" />
    </h:form>
</h:body>
</html>

My managed bean:

@ManagedBean
@ViewScoped
public class DateTestBean {

    private Date date1;

    private Date date2;

    public Date getDate1() {
        return date1;
    }

    public void setDate1(Date date1) {
        this.date1 = date1;
    }

    public Date getDate2() {
        return date2;
    }

    public void setDate2(Date date2) {
        this.date2 = date2;
    }

    public void submit() {
        System.out.println("Submited " + date1 + " " + date2);
    }

}

My validator class:

@FacesValidator(value = "dateValidator")
public class DateValidator implements Validator {

    @Override
    public void validate(FacesContext context, UIComponent component,
            Object value) throws ValidatorException {
        System.out.println(((UIInput) context.getViewRoot().findComponent(
                "date_form:date1input")).getValue());
        UIInput date = (UIInput) component.getAttributes().get("date1");
        System.out.println(date);
        //Will perform date comparison
    }
}

The output I get here sending 2013-10-10 as first date and 2013-10-12 as second one is:

Thu Oct 10 00:00:00 CEST 2013

null

Submited Thu Oct 10 00:00:00 CEST 2013 Sat Oct 12 00:00:00 CEST 2013

Which suggests f:attribute tag is not working for p:calendar component as it does for other input components. However, I can access the first calendar via view root, but enforces me to know the entire path of the client id of the component I want to validate. Both values are set in the managed bean later with no issues.

A workaround would be to use an ajax call when first date changes in order to place it in the model and send it as date itself in the f:attribute instead of sending the UIInput (I'm already doing it that way).

Isn't there a better way? Perhaps I have to join them in a single component like in this case?

2
The #{date1input} doesn't refer id="date1input", instead it's supposed to refer binding="#{date1input}". Check those examples more carefully.BalusC
possible duplicate of Date range validationBalusC
That's true! I haven't seen that, it makes sense because what you're passing is the binded component's reference instead of the id itself. But also enforces you to have it binded, which is IMHO a negative point. Probably the best bet is implementing a double input based component and validating it in one go.Xtreme Biker
As to your apparent aversion against using binding: carefully read this how to properly use it. It's indeed evil when misunderstood and misused. As to "best" approach, have you looked at the OmniFaces way in the linked duplicate? No binding, no f:attribute, no other mess. Just a <o:validateOrder components="startDateId endDateId" /> tag and that's it.BalusC
Many thanks @BalusC, Omnifaces helped me to achieve that. However it seems to break when at least one of the components is null (NPE). My components are not required, I only want to check their order if both of them are not null. It seems not to be specified in the documentation, neither in validateOrder or validateAll. Anyway, thanks for your contribution!Xtreme Biker

2 Answers

2
votes

From your code,

<p:calendar id="date1input" ... />
<p:calendar ...>
    <f:attribute name="date1" value="#{date1input}" />
</p:calendar>

The #{date1input} isn't represented by id="date1input". As shown on those code examples, it's represented by binding="#{date1input}".

Fix it accordingly:

<p:calendar binding="#{date1input}" ... />
<p:calendar ...>
    <f:attribute name="date1" value="#{date1input}" />
</p:calendar>

See also:

1
votes

Another solution is to set values to null at startup, and on the initial state of the components the second calendar is disabled, and set its mindate to the value of the first one.

                            <p:calendar
                                id="startdate"
                                value="#{bean.startdate}">
                                <p:ajax
                                    event="dateSelect"
                                    listener="#{origemBean.listener}"
                                    update="@form" />
                            </p:calendar>                   
                            <p:calendar
                                id="enddate"
                                disabled="#{empty bean.startdate}"
                                value="#{bean.enddate}"
                                mindate="#{bean.startdate}">
                                <p:ajax
                                    event="dateSelect"
                                    listener="#{bean.listener}"
                                    update="@form" />
                            </p:calendar>

On the managed bean, just verify if the startdate is after enddate and treat them.