将 f:attribute 传递给复合实现中的 f:ajax 侦听器

Pass f:attribute to f:ajax listener inside composite implementation

我有以下复合组件(仅相关代码):

    <cc:interface>
        <cc:attribute name="message" type="com.virtuafisica.business.problemsmgmt.entity.Message" required="true" />
        <cc:attribute name="notifiedListener" method-signature="void listener(javax.faces.event.AjaxBehaviorEvent)" />
   </cc:interface>

    <cc:implementation> 
        <h:selectBooleanCheckbox value="#{cc.attrs.message.notified}">
            <f:attribute name="msg" value="#{cc.attrs.message}" />
            <c:if test="#{cc.getValueExpression('notifiedListener') != null}">
                <f:ajax event="change" listener="#{cc.attrs.notifiedListener}" />
             </c:if>
        </h:selectBooleanCheckbox>   
    </cc:implementation>

此组合是从 Facelet 调用的:

<my:message message="#{msg}" notifiedListener="#{problem.changeNotified}"/>

更改事件在支持 bean 中的处理方式如下:

public void changeNotified(AjaxBehaviorEvent event) {
    Message message = (Message) event.getComponent().getAttributes().get("msg");
        if (message == null) {
            logger.log(Level.FINEST, "Null message!");
            return;
        }
        ....
    }

问题是支持 bean 中的消息是 null,尽管 event.getComponent() 是正确的(是 HTMLSelectBooleanCheckbox)。

为什么没有设置属性?

谢谢大家

属性已设置(您可以通过确定 getValueExpression("msg") 没有 return null 来确认),但属性值只是评估 null。延迟表达式会在您想要获取值的那一刻进行评估。因此,#{cc.attrs.message} 恰好在您在 ajax 侦听器方法中调用 get("msg") 的那一刻被评估。但是,此时 #{cc} 不存在于 EL 范围内的任何地方。

解决该技术问题的方法是手动将其放回 EL 范围(然后进行清理!)。

UIComponent component = event.getComponent();
UIComponent cc = UIComponent.getCompositeComponentParent(component);
Map<String, Object> requestScope = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();

try {
    requestScope.put("cc", cc);
    Message message = (Message) component.getAttributes().get("msg");
}
finally {
    requestScope.remove("cc");
}

另一种解决方法是直接从复合材料中获取它。

UIComponent cc = UIComponent.getCompositeComponentParent(event.getComponent());
Message message = (Message) cc.getAttributes().get("msg");

无论哪种方式,这都只是相当笨拙,因为支持 bean 不需要知道复合调用了 ajax 方法。简而言之,您遇到了设计问题。

考虑创建一个支持组件并从那里委托 ajax 调用,这样您就可以直接将 Message 作为方法参数传递。

@FacesComponent("yourCompositeName")
public class YourComposite extends UINamingContainer {

    public void notifiedListener(AjaxBehaviorEvent event) {
        MethodExpression notifiedListener = (MethodExpression) getAttributes().get("notifiedListener");
        Message message = (Message) getAttributes().get("message");
        notifiedListener.invoke(getFacesContext().getELContext(), new Object[] { message });
    }

}

将其注册为 <cc:interface componentType>,更改方法属性以采用 Message 参数并让 <f:ajax> 调用上述支持组件的侦听器方法,后者将依次委托到方法属性。

<cc:interface componentType="yourCompositeName">
    <cc:attribute name="message" type="com.virtuafisica.business.problemsmgmt.entity.Message" required="true" />
    <cc:attribute name="notifiedListener" method-signature="void listener(com.virtuafisica.business.problemsmgmt.entity.Message)" />
</cc:interface>

<cc:implementation> 
    <h:selectBooleanCheckbox value="#{cc.attrs.message.notified}">
        <c:if test="#{cc.getValueExpression('notifiedListener') != null}">
            <f:ajax listener="#{cc.notifiedListener}" />
        </c:if>
    </h:selectBooleanCheckbox>   
</cc:implementation>

(注意:我删除了 event="change",因为那是 checkboxes/radiobuttons 的错误值,默认值已经是 valueChange,这是正确的 - 它评估event="click" checkboxes/radiobuttons)

现在你可以在 bean 中拥有这个。

public void changeNotified(Message message) {
    // ...
}