Проверка поперечного поля в jsf h: datatable с использованием p: calendar

Я заметил, что этот вопрос задан, но на него не ответил правильно.

У меня есть datatable, который имеет два столбца дата начала и дата окончания. Оба содержат элементы p: календарные элементы в них. Мне нужно убедиться, что для каждой строки, что дата в столбце1 не будет после даты в столбце2. Я хотел бы привязать это к структуре проверки JSF, но у меня возникли проблемы.

Я пробовал маркировать datatable rowStatePreserved = "true", это позволяет мне получать значения, но что-то по-прежнему не так, как при неудаче, все значения в первой строке перезаписывают все остальные значения. Что я делаю неправильно, или я должен использовать совершенно другую стратегию?

xhtml code

    <h:form>
 <f:event type="postValidate" listener="#{bean.doCrossFieldValidation}"/>
       <p:dataTable id="eventDaysTable" value="#{course.courseSchedules}" var="_eventDay" styleClass="compactDataTable"
                                 >
                        <p:column id="eventDayStartColumn">
                            <f:facet name="header">
                                Start
                            </f:facet>
                            <p:calendar id="startDate" required="true"  value="#{_eventDay.startTime}" pattern="MM/dd/yyyy hh:mm a"/>
                        </p:column>
                        <p:column id="eventDayEndColumn">
                            <f:facet name="header">
                                End
                            </f:facet>
                            <p:calendar id="endDate" required="true"  value="#{_eventDay.endTime}" pattern="MM/dd/yyyy hh:mm a"/>
                        </p:column>                                        
                    </p:dataTable>
        </h:form>

validationCode

 public void doCrossFieldValidation(ComponentSystemEvent cse) {


        UIData eventsDaysStable = (UIData) cse.getComponent().findComponent("eventDaysTable");

        if (null != eventsDaysStable && eventsDaysStable.isRendered()) {

            Iterator<UIComponent> startDateCalendarIterator = eventsDaysStable.findComponent("eventDayStartColumn").getChildren().iterator();
            Iterator<UIComponent> endDateCalendarIterator = eventsDaysStable.findComponent("eventDayEndColumn").getChildren().iterator();

            while (startDateCalendarIterator.hasNext() && endDateCalendarIterator.hasNext()) {
                org.primefaces.component.calendar.Calendar startDateComponent = (org.primefaces.component.calendar.Calendar) startDateCalendarIterator.next();
                org.primefaces.component.calendar.Calendar endDateComponent = (org.primefaces.component.calendar.Calendar) endDateCalendarIterator.next();

                Date startDate = (Date) startDateComponent.getValue();
                Date endDate = (Date) endDateComponent.getValue();


                if (null != startDate && null != endDate && startDate.after(endDate)) {
                    eventScheduleChronologyOk = false;
                    startDateComponent.setValid(false);
                    endDateComponent.setValid(false);
                }

            }

            if (!eventScheduleChronologyOk) {
                showErrorMessage(ProductManagementMessage.PRODUCT_SCHEDULE_OUT_OF_ORDER);
            }

        }

    }

Ответ 1

Что я делаю неправильно

Выполнение проверки вне контекста данных в нестандартном JSF-способе. Данные строки доступны только , в то время как вы (или JSF) выполняете итерацию по данным datatable. Там физически только один компонент <p:calendar> в каждом столбце, который имеет несколько разных состояний в зависимости от текущей круглой итерации, проходящей по дате. Эти состояния недоступны, если вы не выполняете повторную проверку данных. Вы получите только null как значение.

Технически, с вашим различным подходом к проверке до сих пор вы должны использовать метод visitTree() для компонента UIData и выполнять задание в VisitCallback реализация. Это приведет к переходу через данные.

Например,

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;
    }
});

Это только неуклюже. Это дает вам каждую строку, вам нужно будет поддерживать и проверять индекс строки самостоятельно и собирать значения. Обратите внимание, что вызов UIInput#setValid() также должен выполняться внутри реализации VisitCallback.


или я должен использовать совершенно другую стратегию?

Да, используйте обычный Validator стандартный способ JSF. Вы можете передать один компонент как атрибут другого компонента.

например.

<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>                                        

с

@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));
        }
    }

}

Поскольку оба компонента находятся в одной строке, startDateComponent будет "автоматически" возвращать правильное значение на getValue() каждый раз, когда вызывается этот валидатор. Обратите внимание, что этот валидатор также можно использовать за пределами данных, тогда как ваш первоначальный подход не является.

В качестве альтернативы вы можете использовать OmniFaces <o:validateOrder> как полное решение. Его пример showcase даже показывает этот конкретный пример использования <p:calendar> компонентов внутри <p:dataTable>.