【问题标题】:JSF: how avoid 'Component ID has already been found in the view.' error on double insert of composite componentJSF:如何避免“组件 ID 已在视图中找到。”复合元件双插入错误
【发布时间】:2017-09-12 16:42:51
【问题描述】:

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

我在模板中有一个高度可重用的h: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 操作,例如来自嵌入式链接表编辑器的这个:

 <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.xhtml 客户端页面中执行插入时,我都必须为插入的 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. 模板中没有操作栏两次。这是不可接受的,它只是通过避免它来破坏该功能。

  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 对,同时仍然能够注入任何兼容的 #{manager}

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

<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,以便它可以出现在模板中的 2 个不同位置,而不会出现冲突的组件 id 错误?

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


编辑:这种封装我的“双动作栏”模式的尝试似乎不起作用。来自/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>

似乎被默默地忽略了。

【问题讨论】:

  • 您是否考虑过使用composite:insertChildren 并将您的所有编辑组件嵌套在包含上下按钮的合成中?
  • 欢迎所有建议,但我看不出composite:insertChildren 有什么帮助。我已经使用它来将特定于编辑特定实体类型(以及匹配的支持 bean manager)的方面注入到问题中描述的系统中包含的编辑 CC(尽管此处未显示),并且已经在上层之间和下按钮。上下按钮总是相同的(对于给定的页面),但我将不同的按钮集(以及它们匹配的manager)放入不同实体类型的相同模板中(通过edit.xhtml 的变体)。会思考。
  • 我指的是重构您的页面,以便您的表单组件可以嵌套在包含您的上下按钮的 CC 中,但如果您将子项插入其中,它可能不适合您。我想您可能已经想到了这一点,但是如果您不需要支持小型设备(手机),我使用了一个固定的 div 来按住中间按钮(保存等)来实现您所追求的长的多页表单。

标签: jsf jsf-2.3


【解决方案1】:

我已经找到了解决我自己问题的解决方案(实际上是解决方法),以满足我的要求。不漂亮,但可能对其他人有用。

我没有测试特定的组件 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 测试正确失败,因为两个“保存”按钮都需要。

【讨论】:

    猜你喜欢
    • 2023-03-19
    • 1970-01-01
    • 2011-12-28
    • 2017-10-23
    • 2016-01-20
    • 2012-07-28
    • 2011-06-04
    • 2011-10-01
    • 2014-03-22
    相关资源
    最近更新 更多