Cross field validation in jsf h:datatable using p:calendar

前端 未结 1 1043
-上瘾入骨i
-上瘾入骨i 2021-02-01 09:31

I noticed this question was asked, but it has not been answered correctly.

I have a datatable that has two columns start date and end date

相关标签:
1条回答
  • 2021-02-01 10:25

    What am I doing wrong

    Performing validation outside the context of the datatable in a non-standard JSF way. The row data is only available while you're (or JSF is) iterating over the datatable. There's physically only one <p:calendar> component in every column which has multiple different states depending on the current datatable iteration round. Those states are not available when you're not iterating over the datatable. You'll only get null as value then.

    Technically, with your different validation approach so far, you should be invoking visitTree() method on the UIData component and performing the job in the VisitCallback implementation. This will iterate over the datatable.

    For example,

    dataTable.visitTree(VisitContext.createVisitContext(), new VisitCallback() {
        @Override
        public VisitResult visit(VisitContext context, UIComponent component) {
            // Check if component is instance of <p:calendar> and collect its value by its ID.
    
            return VisitResult.ACCEPT;
        }
    });
    

    This is only clumsy. This gives you every single row, you'd need to maintain and check the row index youself and collect the values. Note that calling UIInput#setValid() should also be done inside the VisitCallback implementation.


    or should I be using a completely different strategy?

    Yes, use a normal Validator the standard JSF way. You can pass the one component as an attribute of the other component.

    E.g.

    <p:column>
        <p:calendar binding="#{startDateComponent}" id="startDate" required="true"  value="#{item.start}" pattern="MM/dd/yyyy hh:mm a"/>
    </p:column>
    <p:column >
        <p:calendar id="endDate" required="true"  value="#{item.end}" pattern="MM/dd/yyyy hh:mm a">
            <f:validator validatorId="dateRangeValidator" />
            <f:attribute name="startDateComponent" value="#{startDateComponent}" />
        </p:calendar>
    </p:column>                                        
    

    with

    @FacesValidator("dateRangeValidator")
    public class DateRangeValidator implements Validator {
    
        @Override
        public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
            if (value == null) {
                return; // Let required="true" handle.
            }
    
            UIInput startDateComponent = (UIInput) component.getAttributes().get("startDateComponent");
    
            if (!startDateComponent.isValid()) {
                return; // Already invalidated. Don't care about it then.
            }
    
            Date startDate = (Date) startDateComponent.getValue();
    
            if (startDate == null) {
                return; // Let required="true" handle.
            }
    
            Date endDate = (Date) value;
    
            if (startDate.after(endDate)) {
                startDateComponent.setValid(false);
                throw new ValidatorException(new FacesMessage(
                    FacesMessage.SEVERITY_ERROR, "Start date may not be after end date.", null));
            }
        }
    
    }
    

    As both components are in the same row, the startDateComponent will "automagically" give the right value back on getValue() everytime this validator is invoked. Note that this validator is also reuseable outside the datatable while your initial approach isn't.

    Alternatively, you can use OmniFaces <o:validateOrder> as a complete solution. Its showcase example even shows off this specific use case of <p:calendar> components inside a <p:dataTable>.

    0 讨论(0)
提交回复
热议问题