JSF复合组件目标操作在c:forEach标记内失败
我们在c:forEach标记内使用commandButtons,其中按钮的动作接收forEach var属性作为参数,如下所示:
<c:forEach var="myItem" items="#{myModel}"> <h:commandButton action="#{myController.process(myItem)}" value="#{myItem.name}" /> </c:forEach> 这很好用.如果我们将commandButton包装在一个复合组件中,它就不再起作用了:控制器被调用,但参数始终为null. <c:forEach var="myItem" items="#{myModel}"> <h:commandButton action="#{myController.process(myItem)}" value="#{myItem.name}" /> <my:mybutton action="#{myController.process(myItem)}" value="#{myItem.name}" /> </c:forEach> 以下我的:mybutton实现: <composite:interface> <composite:attribute name="action" required="true" targets="button" /> <composite:attribute name="value" required="true" /> </composite:interface> <composite:implementation> <h:commandButton id="button" value="#{cc.attrs.value}"> </h:commandButton> </composite:implementation> 请注意,按钮的value属性(也绑定到c:ForEach var)可以正常工作.只有通过复合组件的目标机制传播的动作才能正确评估. 我们在mojarra 2.2.8,el-api 2.2.5,tomcat 8.0. 解决方法
这是由Mojarra中的错误引起的,或者可能是JSF规范中关于复合组件的重定向方法表达式的疏忽引起的.
解决方法是下面的ViewDeclarationLanguage. public class FaceletViewHandlingStrategyPatch extends ViewDeclarationLanguageFactory { private ViewDeclarationLanguageFactory wrapped; public FaceletViewHandlingStrategyPatch(ViewDeclarationLanguageFactory wrapped) { this.wrapped = wrapped; } @Override public ViewDeclarationLanguage getViewDeclarationLanguage(String viewId) { return new FaceletViewHandlingStrategyWithRetargetMethodExpressionsPatch(getWrapped().getViewDeclarationLanguage(viewId)); } @Override public ViewDeclarationLanguageFactory getWrapped() { return wrapped; } private class FaceletViewHandlingStrategyWithRetargetMethodExpressionsPatch extends ViewDeclarationLanguageWrapper { private ViewDeclarationLanguage wrapped; public FaceletViewHandlingStrategyWithRetargetMethodExpressionsPatch(ViewDeclarationLanguage wrapped) { this.wrapped = wrapped; } @Override public void retargetMethodExpressions(FacesContext context,UIComponent topLevelComponent) { super.retargetMethodExpressions(new FacesContextWithFaceletContextAsELContext(context),topLevelComponent); } @Override public ViewDeclarationLanguage getWrapped() { return wrapped; } } private class FacesContextWithFaceletContextAsELContext extends FacesContextWrapper { private FacesContext wrapped; public FacesContextWithFaceletContextAsELContext(FacesContext wrapped) { this.wrapped = wrapped; } @Override public ELContext getELContext() { boolean isViewBuildTime = TRUE.equals(getWrapped().getAttributes().get(IS_BUILDING_INITIAL_STATE)); FaceletContext faceletContext = (FaceletContext) getWrapped().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); return (isViewBuildTime && faceletContext != null) ? faceletContext : super.getELContext(); } @Override public FacesContext getWrapped() { return wrapped; } } } 在faces-config.xml中按如下所示安装: <factory> <view-declaration-language-factory>com.example.FaceletViewHandlingStrategyPatch</view-declaration-language-factory> </factory> 我怎么把它钉了下来? 我们已经确认问题是当在复合组件中调用操作时,方法表达式参数变为null,而操作本身在另一个复合组件中声明. <h:form> <my:forEachComposite items="#{['one','two','three']}" /> </h:form> <cc:interface> <cc:attribute name="items" required="true" /> </cc:interface> <cc:implementation> <c:forEach items="#{cc.attrs.items}" var="item"> <h:commandButton id="regularButton" value="regular button" action="#{bean.action(item)}" /> <my:buttonComposite value="cc button" action="#{bean.action(item)}" /> </c:forEach> </cc:implementation> <cc:interface> <cc:attribute name="action" required="true" targets="compositeButton" /> <cc:actionSource name=""></cc:actionSource> <cc:attribute name="value" required="true" /> </cc:interface> <cc:implementation> <h:commandButton id="compositeButton" value="#{cc.attrs.value}" /> </cc:implementation> 我做的第一件事就是找到负责在#{bean.action(item)}后面创建 在调用堆栈中,我们可以检查ELContext#getVariableMapper()以及谁负责创建MethodExpression.在具有复合组件的测试页面中依次嵌套一个常规命令按钮和一个复合命令按钮,我们可以在ELContext中看到以下区别: 常规按钮: 我们可以看到常规按钮使用DefaultFaceletContext作为ELContext,并且VariableMapper包含正确的项目变量. 复合按钮: 我们可以看到复合按钮使用标准ELContextImpl作为ELContext,并且VariableMapper不包含正确的项变量.因此,我们需要在调用堆栈中返回一些步骤,以查看此标准ELContextImpl的来源以及为何使用它而不是DefaultFaceletContext. 一旦找到创建特定ELContext实现的位置,我们就可以发现它是从FacesContext#getElContext()获得的.但这并不代表复合组件的EL上下文!这由当前的FaceletContext表示.所以我们需要再回过头来弄清楚FaceletContext没有被传递下去的原因. 我们可以在这里看到,CompositeComponentTagHandler#applyNextHander()不会通过FaceletContext而是通过FacesContext.这是JSF规范中可能存在疏忽的确切部分. ViewDeclarationLanguage#retargetMethodExpressions()应该要求另一个参数,表示所涉及的实际ELContext. 但是它就是这样啊.我们现在无法动态更改规格.我们能做的最好的就是向他们报告问题. 上面显示的FaceletViewHandlingStrategyPatch通过最终覆盖FacesContext#getELContext()的方式如下所示: @Override public ELContext getELContext() { boolean isViewBuildTime = TRUE.equals(getWrapped().getAttributes().get(IS_BUILDING_INITIAL_STATE)); FaceletContext faceletContext = (FaceletContext) getWrapped().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY); return (isViewBuildTime && faceletContext != null) ? faceletContext : super.getELContext(); } 你看,它检查JSF当前是否正在构建视图以及是否存在FaceletContext.如果是,则返回FaceletContext而不是标准ELContext实现(注意 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |