1

全部

我一直在研究一个日期范围的复合组件。本质上,我的复合组件在下面使用两个 Richfaces 4.3 日历组件来捕获各个日期值,生成日期范围(一对 LocalDate 对象)。我发现这个博客条目是我的自定义组件的基础,它将日历上的两个提交的值组合成一对值。

一切似乎都运行良好,并且值正在更新。但是,我试图弄清楚如何将更改事件传播到使用 xhtml 页面以部分呈现另一个组件,但我没有成功。我已经尝试了我能想到的一切,但我认为我错过了一些东西。

这页纸:

<rich:panel>
    <f:facet name="header">Calendar Date Range Component</f:facet>
    <h:outputText id="out1" binding="#{calendarDateRangeTestBean.component1}"
                 value="#{calendarDateRangeTestBean.dateRange}" converter="localDatePairConverter" /><br/>
    <h:outputText id="out2" value="#{calendarDateRangeTestBean.dateRange}" converter="localDatePairConverter" /><b>NOT WORKING</b>
    <yxp:calendarDateRange id="calendarDateRange" value="#{calendarDateRangeTestBean.dateRange}"
        dataModel="#{calendarDateRangeTestBean}"
            valueChangeListener="#{calendarDateRangeTestBean.processValueChange}">
        <f:ajax execute="@all" listener="#{calendarDateRangeTestBean.processBehaviorEvent}"/>   

        <!-- This doesn't seem to work???? -->
        <f:ajax execute="@all" render="out2" />
    </yxp:calendarDateRange>
</rich:panel>

我的测试托管bean:

@ViewScoped
@ManagedBean
public class CalendarDateRangeTestBean extends AbstractCalendarDateRangeDataModel implements
        ValueChangeListener, Serializable {

   private static Logger logger = LoggerFactory.getLogger(CalendarDateRangeTestBean.class);

   private Pair<LocalDate> dateRange = Pair.of(LocalDate.now(), LocalDate.now().plusDays(7));

   private UIComponent component1;

   public UIComponent getComponent1() {
       return component1;
   }

   public LocalDateRange getDateRange() {
       return dateRange;
   }

   public void processBehaviorEvent(final javax.faces.event.AjaxBehaviorEvent event) {

       logger.info("processing event " + event + ": " + event.getBehavior());

       final FacesContext context = FacesContext.getCurrentInstance();
       logger.info("Setting render to " + component1.getClientId(context));

       // This seems to cause a rerender of the first component
       context.getPartialViewContext().getRenderIds().add(component1.getClientId(context));

   }

   @Override
   public void processValueChange(final ValueChangeEvent event) throws AbortProcessingException {
       logger.info(this.toString() + ": processing value change event " + event + ": ["
            + event.getOldValue() + ":" + event.getNewValue() + "]");
   }

   public void setComponent1(final UIComponent component1) {
       this.component1 = component1;
   }

   public void setDateRange(final Pair<LocalDate> dateRange) {
       logger.info("Setting date range to " + dateRange);
       this.dateRange = dateRange;
   }

}

我的复合组件:

<ui:composition 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:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
xmlns:composite="http://java.sun.com/jsf/composite">

<!-- Methods exposed on rich:component are available in the __proto__ object. -->

<composite:interface componentType="com.yieldex.platform.ui.CalendarDateRange">
    <composite:attribute name="value" required="true" type="demo.Pair"/>
    <composite:attribute name="dataModel" required="false" type="demo.Pair" />
    <composite:clientBehavior name="change" event="change" targets="startCalendar endCalendar" default="true"/>
</composite:interface>

<composite:implementation>

    <h:outputStylesheet library="yieldex/platform" name="css/yieldex-platform.css" target="head" />

    <div id="#{cc.clientId}" class="yxp-calendar-date-range">
        <rich:calendar id="startCalendar" 
            binding="#{cc.startCalendar}"
            styleClass="yxp-start-date-range" 
            converter="localDateConverter" mode="ajax"
            dataModel="#{not empty cc.attrs.dataModel ? cc.attrs.dataModel.startCalendarDataModel : standardCalendarDateRangeDataModel.startCalendarDataModel}"
            monthLabels="#{dateRangeMessages.monthNames}"
            weekDayLabelsShort="#{dateRangeMessages.weeksShort}"
            monthLabelsShort="#{dateRangeMessages.monthNames}" popup="false"
            showInput="false" showFooter="false" showWeeksBar="false"
            showWeekDaysBar="true" showApplyButton="false"
            buttonIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}"
            buttonDisabledIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}">

            <f:facet name="weekDays"></f:facet>
            <f:ajax immediate="true" execute="@all" render="@this endCalendar"/>
        </rich:calendar>
        <rich:calendar id="endCalendar" 
            binding="#{cc.endCalendar}"
            styleClass="yxp-end-date-range" 
            converter="localDateConverter" mode="ajax"
            dataModel="#{not empty cc.attrs.dataModel ? cc.attrs.dataModel.endCalendarDataModel : standardCalendarDateRangeDataModel.endCalendarDataModel}"
            monthLabels="#{dateRangeMessages.monthNames}"
            weekDayLabelsShort="#{dateRangeMessages.weeksShort}"
            monthLabelsShort="#{dateRangeMessages.monthNames}" popup="false"
            showInput="false" showFooter="false" showWeeksBar="false"
            showWeekDaysBar="true" showApplyButton="false"
            buttonIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}"
            buttonDisabledIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}">

            <f:facet name="weekDays"></f:facet>
            <f:ajax immediate="true" execute="@all" render="startCalendar @this"/>
        </rich:calendar>
    </div>

</composite:implementation>
</ui:composition>

我的支持组件:

@FacesComponent("com.yieldex.platform.ui.CalendarDateRange")
public class YXCalendarDateRange extends UIInput implements NamingContainer {

    private UICalendar startCalendarComponent;
    private UICalendar endCalendarComponent;

    @Override
    public void encodeBegin(final FacesContext context) throws IOException {

        final Pair<LocalDate> value = (Pair<LocalDate>) this.getValue();
        if (value == null) {
            startCalendarComponent.setValue(null);
            endCalendarComponent.setValue(null);
        } else {
            startCalendarComponent.setValue(value.getStart());
            endCalendarComponent.setValue(value.getEnd());
        }

        super.encodeBegin(context);
    }

    @Override
    protected Object getConvertedValue(final FacesContext context, final Object submittedValue) {

        final LocalDate startDate = (LocalDate) startCalendarComponent.getConverter().getAsObject(context,
                startCalendarComponent, (String) this.startCalendarComponent.getSubmittedValue());

        final LocalDate endDate = (LocalDate) endCalendarComponent.getConverter().getAsObject(context,
                endCalendarComponent, (String) this.endCalendarComponent.getSubmittedValue());

       if (startDate == null || endDate == null) {
            return null;
       } else {

            if (startDate.isAfter(endDate)) {
                final FacesMessage message = new FacesMessage();
                message.setSeverity(FacesMessage.SEVERITY_ERROR);
                message.setSummary("start date cannot be after end date");
                message.setDetail("start date cannot be after end date");
                throw new ConverterException(message);
            }

            return Pair.of(startDate, endDate);
        }
    }

    public UICalendar getEndCalendar() {
        return this.endCalendarComponent;
    }

    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    public UICalendar getStartCalendar() {
        return this.startCalendarComponent;
    }

    @Override
    public Object getSubmittedValue() {
       return this;
    }

    public void setEndCalendar(final UICalendar endCalendarComponent) {
        this.endCalendarComponent = endCalendarComponent;
    }

    public void setStartCalendar(final UICalendar startCalendarComponent) {
        this.startCalendarComponent = startCalendarComponent;
    }
}

我看到的是 valueChangedEvent 即将到来。我还看到我的 processBehaviorEvent 被调用,并且当我以编程方式调用它时,第一个 outputText 被重新呈现。但是第二个似乎没有重新渲染。我试图弄清楚这是否是 Mojarra 2.1.25 中的一个错误,或者我的方法是否存在根本性的问题。任何建议将不胜感激。

4

1 回答 1

1

中的任何客户端 ID<f:ajax render>都相对于它所附加到的组件的父命名容器进行评估。在这个构造中,<f:ajax>最终附加在复合组件内,复合组件本身就是一个命名容器。但是,组合内部没有具有 ID 的组件out2,这就是问题所在。

要解决此问题,请指定绝对客户端 ID。例如,当它在<h:form id="formId">元素内部时:

<f:ajax execute="@all" render=":formId:out2" />

如果更复杂,将组件绑定到视图并动态引用其客户端 ID:

<h:outputText id="out2" binding="#{out2}" ... />
...
<f:ajax execute="@all" render=":#{out2.clientId}" />

也可以看看:

于 2013-08-09T15:21:19.877 回答