JSF:如何避免双重插入复合组件时出现 'Component ID has already been found in the view.' 错误

JSF: how avoid 'Component ID has already been found in the view.' error on double insert of composite component

我知道导致以下问题的原因。我正在寻找一种优雅的方法来解决问题,而不是我找到的蛮力(和非 DRY)解决方法。

我在模板中有一个高度可重用的 h:form(我知道 "god forms" 的危险,这不是一个很好的形式),我插入 editable 内容通过模板添加到该表单中,页面顶部和底部都有一个 相同 命令按钮操作栏。 (此模板有数百个客户端,其中一些会在模板中插入不同的操作栏。)

我这样做(顶部和底部栏)的唯一原因是用户方便;我发现在使用许多内容管理系统时,必须向下滚动或向上滚动才能在长表单上找到“保存”按钮(或操作栏中的其他按钮)很烦人。

模板(请不要告诉我它是 "god form")有:

<h:form prependId=".." id="form">

 <div id="actions-upper" class="actions">
   <ui:insert name="actions"/>
 </div> 

... other reusable stuff

 <div id="actions-lower" class="actions">
   <ui:insert name="actions"/>
 </div>

</h:form>

作为模板客户端的每个 edit.xhtml 页面(有很多)都会插入操作栏,以及兼容的 #{manager} 支持 bean 参数:

<ui:composition template="/template.xhtml">
...
    <ui:define name="actions">
        <util:edit_actions id="edit_actions" manager="#{manager}"/>
    </ui:define>

 ... other insertions with editable content omitted

注意上面我是如何给 CC `util:edit_actions' 一个 id(我直到最近才在这个 CC 上这样做,原因我将在下面解释)。

因此您可以看到完全相同的操作工具栏被插入到页面的表单部分的顶部和底部。但是,如果您按照上面显示的方式执行此操作并为 edit_actions 传递了一个 id,您将得到:

javax.servlet.ServletException: Component ID edit_actions has already been found in the view.  

多年来我一直在成功使用此模板,直到我引入显式 ID,原因如下所示。

edit_actions CC 有一些命令按钮,例如:

 <composite:implementation>
  <p:toolbar>
   <p:toolbarGroup>
    <p:commandButton 
      ajax ="true"
      action="#{cc.attrs.manager.crud.update}"
      value="Save" 
      update="@form"
      id="save"
     />
         ...

现在一般的“保存”按钮并不总是表单中的唯一按钮;有时会有其他按钮使用 conditionally required input fields 执行临时 AJAX 操作,例如来自嵌入式链接 table 编辑器的这个:

 <p:inputText 
   id="newLinkUrl"
   value="#{cc.attrs.manager.newLinkUrl}"
   required="#{param['edit_actions:save']==null}"
   validator="urlValidator"
 />

<p:commandButton                        
   value="Add new link !"
   action="#{cc.attrs.manager.addNewLink(cc.attrs.element)}"
   update="... newLinkUrl ..."
  />

(顺便说一句,urlValidator 不会抛出 null,系统依赖于条件 required,以便一般 @form 保存始终有效。)

但要获得工作所需的条件:

   required="#{param['edit_actions:save']==null}"

每当在使用它的 数百 个客户端页面中执行插入时,我必须给插入的 edit_actions CC 一个显式 ID :

<ui:composition template="/template.xhtml">
...
    <ui:define name="actions">
        <util:edit_actions id="edit_actions" manager="#{manager}"/>
    </ui:define>

但如上所示,如果我在此处包含 id,现在会导致错误(但没有它我无法使用条件 required 技巧)。

到目前为止我发现了两种解决方法:

  1. 干脆不要模板中的action bar两次。这是unacceptable,它只是通过避免它来破坏该功能。

  2. 模板中有 2 个不同的插入点确实有效,但您必须小心 ID。

第二个有问题:

<ui:composition template="/template.xhtml">
 <ui:define name="actions_upper">
  <util:edit_actions id="edit_actions_upper" manager="#{manager}"/>
 </ui:define>
 <ui:define name="actions_lower">
  <util:edit_actions id="edit_actions_lower" manager="#{manager}"/>
 </ui:define>

请注意,上面的代码不是 Don't Repeat Yourself (DRY) 代码,我认为这是最重要的编码实践之一,而且 JSF 通常特别擅长解决这些问题。确保上述模板插入模式和 id 模式在数百个 edit.xhtml 页面中得到解决,这很容易出错,除非我能以某种方式封装 ui:define 对,同时仍然能够注入任何兼容的 [=24] =].

然后条件要求测试必须同时测试上下保存按钮:

<p:inputText 
  id="newLinkUrl"
  value="#{cc.attrs.manager.newLinkUrl}"
  required="#{param['edit_actions_upper:save']==null and param['edit_actions_lower:save']==null}"
  validator="urlValidator"
 />

总而言之,这是一个相当丑陋的非 DRY 解决方法。

Q1:有什么方法可以自动动态更改插入的 edit_action.xhtml 的 ID,以便它可以出现在模板中的两个不同位置,而不会出现组件 ID 冲突错误?

Q2:或者,有没有什么方法可以将两个 ui:define 封装在上下栏插入的解决方法中(如解决方法 2 中所示),同时仍然能够注入#{manager}(这样我就可以将其包含在模板的数百个 edit.xhtml 客户端中并将其作为封装策略重用)?


编辑:这种封装我的 "double action bar" 模式的尝试似乎没有用。来自 /include/edit_actions_defines.xhtml:

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:util="http://xmlns.jcp.org/jsf/composite/util">

    <ui:define name="actions_upper">
        <util:edit_actions id="edit_actions_upper" manager="#{manager}"/>
    </ui:define>

    <ui:define name="actions_lower">
        <util:edit_actions id="edit_actions_lower" manager="#{manager}"/>
    </ui:define>

</ui:composition>

edit.xhtml 尝试使用:

<ui:composition template="/template.xhtml">

    <ui:include src="/include/edit_actions_defines.xhtml">
        <ui:param name="manager" value="#{specificManager}"/>            
    </ui:include>

好像被默默无视了

我找到了满足我的要求的解决方案(真正的解决方法)。不漂亮,但可能对其他人有用。

我没有测试特定的组件 ID,而是使用正则表达式:

/**
 * Searches for a component id ending in ":save".
 * 
 * @return 
 */
public boolean isSaveButtonPressed() {        
    Map<String, String> parameterMap = FacesContext.getCurrentInstance()
            .getExternalContext().getRequestParameterMap();
    for (String key : parameterMap.keySet()) {
        boolean matches = Pattern.matches("^j.*:save",key);
        if (matches) return true;
    }
    return false;
}

所需的测试很简单:

<p:inputText 
  id="newLinkUrl"
  value="#{cc.attrs.manager.newLinkUrl}"
  required="#{not cc.attrs.manager.saveButtonPressed}"
  validator="urlValidator"
/>

然后在插入我的 util:edit_actions 组件时(为了用户方便,它会通过模板出现在表单的顶部和底部)我没有向它传递一个明确的 ID,我只是让 JSF 生成它们, 并且它们对于两个保存按钮是不同的(不再冲突)。

这样我就可以吃我注射过两次的 edit_actions 蛋糕了。对 URL 字符串字段的 required 测试正确地失败了,正如两个保存按钮所期望的那样。