PrimeFaces 动态菜单:不能删除同一个组件两次和 ID 生成

PrimeFaces dynamic menu: Cannot remove the same component twice and ID generation

我在 <p:dataTable> 中有一个 <p:menu>,在升级到 Mojarra 2.1.22(在 GlassFish 3.1.2.2 上)时,我们变得不规则 FacesExceptions:

        <p:dataTable id="data"
                     widgetVar="resultDataTable" 
                     value="#{depotManager.dataModel}"
                     var="dep"
                     rowKey="#{dep.id}"
                     selection="#{depotManager.selectedEntities}"
                     selectionMode="#{depotManager.singleSelectionMode ? 'single' : 'multiple'}"
                     ...
                     emptyMessage="#{msg['entity.depot.list.emptyMessage']}">

            <p:ajax event="rowSelect"...

            <f:facet name="header">
                <h:panelGroup id="header"
                              layout="block">

                    <p:menu overlay="true"
                            trigger="sub-client-button"
                            my="left top"
                            at="left bottom"
                            model="#{accountManagerMenuKeeper.subClientMenuModel}" />

我们得到的异常是:

javax.faces.FacesException: Cannot remove the same component twice: content-form:data:menu-item-1

类似的问题在这里:http://forum.primefaces.org/viewtopic.php?f=3&t=23669

我做了一些调查,问题似乎是我们在 PrimeFaces 3.5.28 中使用视图范围的 bean。我将菜单 bean 切换为请求范围,如下所述:

http://blog.primefaces.org/?p=2594

我们无法切换到 PrimeFaces 4...一些异常消失了,但不是全部(可能是一半)。所以问题依旧。

现在通过生成唯一 ID 更好地理解了这个问题,阅读 https://java.net/jira/browse/JAVASERVERFACES-2283 ,我可能需要在 dataTable 命名容器下生成 ID。

dataTable 的 ID 是 "content-form:data",所以我想我需要为每个动态菜单项生成唯一的 ID,我认为我目前没有这样做(每个客户端一个菜单项,附加的简单计数器):

        for ( Client subClient : subClients )
        {
            MenuItem item = new MenuItem();
            item.setId( "menu-item-" + ( ++i ) );
//            item.setId( "content-form:data:menu-item-" + ( ++i ) );
            ...
        }

问:

如何在此处生成正确的 ID,例如使用 JSF 的内置方法,如 createUniqueId() 等?

我有点奇怪,因为异常提到前面有数据表ID的完整ID:javax.faces.FacesException: Cannot remove the same component twice: content-form:data:menu-item-1...

即使在 PrimeFaces 3.5.28 上使用简单的请求范围的菜单创建 bean,问题也消失了:

@Named
@RequestScoped
public class AccountManagerMenuKeeper implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Inject
    @SeamLogger
    protected Logger          log;

    @Inject
    protected SessionHelper   sessionHelper;

    @Inject
    private DepotManager      accountManager;

    private MenuModel         subClientMenuModel;

    /**
     * @return the subClientMenuModel
     */
    public MenuModel getSubClientMenuModel()
    {
        if ( this.subClientMenuModel == null )
        {
            buildSubClientMenuModel();
        }

        return ( this.subClientMenuModel );
    }

    /**
     * Builds the menu model for adding sub-clients.
     * 
     */
    private void buildSubClientMenuModel()
    {
        this.subClientMenuModel = new DefaultMenuModel();
        List<Client> subClients = this.accountManager.getWritableSubClients();

        if ( subClients == null || subClients.isEmpty() )
            return;

        // Necessary instances
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ELContext elContext = facesContext.getELContext();
        Application application = facesContext.getApplication();
        ExpressionFactory factory = application.getExpressionFactory();

        int i = 0;

        for ( Client subClient : subClients )
        {
            MenuItem item = new MenuItem();
            item.setId( "menu-item-" + ( ++i ) );

            item.setValue( subClient.getName() );
            item.setProcess( "@this" );
            item.setUpdate( ":content-form" );
            item.setDisabled( this.accountManager.isReadOnly() || !this.accountManager.isSingleSelectionMode() || EMode.ADD.equals( this.accountManager.getMode() ) || EMode.EDIT.equals( this.accountManager.getMode() ) );
            item.setActionExpression( factory.createMethodExpression( elContext, "#{depotManager.add}", String.class, new Class[] {} ) );

            ValueExpression targetExpression = factory.createValueExpression( elContext, "#{depotManager.selectedSubClientId}", Long.class );
            ValueExpression valueExpression = factory.createValueExpression( elContext, "" + subClient.getId(), Long.class );
            item.addActionListener( new SwitchableSetPropertyActionListener( targetExpression, valueExpression ) );

            this.subClientMenuModel.addMenuItem( item );
        }
    }
}

此 will/should 适用于较新的 Mojarra 版本。 (呃)