3

我有一个输入元素数量可变的表单,如下所示:

<ui:repeat var="_lang" value="#{myBean.languages}">
    <h:inputTextarea value="${_lang.title}" id="theTitle" />
    <h:messages for="theTitle"/>
</ui:repeat>

当支持 bean 中的某个方法被触发时,我想向 的第二次迭代添加一条消息ui:repeat,而不是其他的。

我在这里看到了这个问题的不同变体,所有问题似乎都是由于ui:repeatJSF 组件树中的迭代不可用。

到目前为止我已经尝试过:

  • h:inputTextarea将s绑定到Map<String,UIComponent>bean 中的 a。(a) ...使用...binding="#{myBean.uiMap[_lang.id]}"(其中_lang.id是唯一的字符串)。这产生了JBWEB006017: Target Unreachable, ''BracketSuffix'' returned null。(我使用 id 转储了相应的字符串映射,相同的语法在binding) (b) ... 或使用...binding="#{myBean.uiMap.get()}". 这使页面很好,但是当我按下我的方法的按钮时,setter 不会被调用,因此UIComponents 永远不会被添加到Map.

  • 将 s 绑定h:inputTextarea到 bean 中的一个数组UIComponent[],用正确数量的空值预填充它,然后使用行计数器ui:repeat作为 xhtml 文件中的索引。得到空指针异常,数组的设置器从未被调用,因此数组从未填充实际UIComponent的 s。

  • 将外部绑定h:panelGroup到 bean 并尝试在 JSF 树中的子项中递归查找输入元素。只找到一个输入,请参阅上面的“迭代不可用”问题。

  • 我还尝试手动替换ui:repeatc:forEach生成行号(以便它们有望在 JSF 树中可用),但我根本没有得到任何渲染输出。

(注意:目标是显示验证错误消息,但它们必须来自支持 bean。使用f:validator或类似的,甚至是自定义的,并不是一个真正的选择,因为我需要验证支持 bean 中的数据。 )

坦率地说,我没有想法。这不会那么难吧?

编辑:

对于我的第三次尝试,绑定到外部h:panelGroup,这是我的 JSF finder 函数:

private List<UIComponent> findTitleComponents(UIComponent node) {
    List<UIComponent> found = new ArrayList<UIComponent>();
    for (UIComponent child : node.getChildren()) {
        if (child.getId().equals("theTitle")) {
            found.add(child);
            log.debug("have found "+child.getClientId());
        } else {
            found.addAll(findTitleComponents(child));
            log.debug("recursion into "+child.getClientId());
        }
    }
    return found;
}

我在 上调用它,这是.周围的node绑定。(我正在使用递归,因为我的实时应用程序有一个稍微嵌套的结构)我认为这应该给我所有的“theTitle”文本区域,这样我就可以添加消息和读取我喜欢的属性。唉,该方法只返回一个“theTitle”组件,日志消息显示了原因:UIComponenth:panelGroupui:repeat

在生成页面的 DOM 中,id 类似于“myform:myPanelGroup:0:theTitle”(包括 的迭代计数器ui:repeat),而 bean 只看到 getClientId() 之类的myform:myPanelGroup:theTitle- 并且只存在一次,最后一次 (我猜?)迭代。

4

2 回答 2

2

您尝试将输入组件绑定到映射/数组失败,因为 JSF 组件树中没有多个这些组件,而只有一个。<ui:repeat>不会在生成 JSF 组件树的视图构建期间运行。相反,它在视图渲染期间运行,生成 HTML 输出。换句话说,在<ui:repeat>每次迭代生成 HTML 输出时,都会重用 的子组件。

抛出特定异常“Target Unreachable, ''BracketSuffix'' returned null”是因为该变量在视图构建期间不可用,即构建 UI 组件树并评估所有属性的#{_lang}那一刻。它仅在视图渲染期间可用。idbinding

如果您改为使用这些绑定尝试将会成功<c:forEach>。它在视图构建期间运行,生成 JSF 组件树。然后,您最终会得到子组件的物理多个实例,这些子组件又会生成各自的 HTML 输出,而不会被多次重用。

由于前面提到的原因,加入一个小组并试图找到所有孩子显然是行不通的。<ui:repeat>不会在组件树中生成物理上的多个 JSF 组件。相反,它会根据当前迭代轮次的状态重复使用相同的组件来多次生成 HTML 输出。

替换为<c:forEach>应该有效。也许您正面临时间问题,因为它在视图构建期间运行,并且您正在准备模型,例如preRenderView而不是@PostConstruct左右。

如果您仔细阅读 JSF2 Facelets 中的 JSTL,以上所有内容都会更容易理解……有意义吗?


至于您的具体功能要求,您通常会使用 aValidator来完成这项工作。如果您在输入组件上注册它,那么它将在每一轮迭代中被调用。您将立即拥有具有正确状态的正确输入组件作为validate()方法的第二个参数,并将提交/转换的值作为第三个参数。

如果您真的需要事后执行这项工作,例如因为您需要了解所有输入,那么您应该以编程方式迭代<ui:repeat>自己。您可以借助UIComponent#visitTree()它收集输入组件在每一轮迭代中的状态。

例如

final FacesContext facesContext = FacesContext.getCurrentInstance();
UIComponent repeat = getItSomehow(); // findComponent, binding, etc.

repeat.visitTree(VisitContext.createVisitContext(facesContext), new VisitCallback() {
    @Override
    public VisitResult visit(VisitContext context, UIComponent target) {
        if (target instanceof UIInput && target.getId().equals("theTitle")) {
            String clientId = target.getClientId(facesContext);
            Object value = ((UIInput) target).getValue();
            // ...
            facesContext.addMessage(clientId, message);                
        }
        return VisitResult.ACCEPT;
    }
});

也可以看看:

于 2013-10-02T11:37:42.693 回答
0

还有另一种选择:自己替换整个 FacesMessages shebang。与二十一点。和...

无论如何,根据与Johannes Brodwall的讨论,我们选择避免​​整个 visitTree 混乱并建立我们自己的消息机制。这包括:

1) 一个 ViewScoped bean,包含一个 Multimaps 的 Map:

private Map<Object, Multimap<String, String>> fieldValidationMessages = new HashMap<>();

这需要一个作为字段标识符(可以是相应的beanObject本身,一个 UI 组件,甚至是String在运行时在ui:repeat.String

bean 也有方便的方法来获取和设置字段和子字段的消息,以及检查是否存储了任何消息(即是否存在验证错误)。

2) 一个简单的 xhtml 包含,显示给定字段的错误消息,替换h:messages for...

就是这样。问题是它在生命周期的应用程序和渲染阶段而不是 JSF 自己的验证阶段运行。但是由于我们的项目已经决定进行 bean 验证而不是生命周期验证,所以这不是问题。

于 2013-12-03T15:14:31.223 回答