如何使用PrimeFaces实现递归菜单

时间:2012-11-29 05:16:10

标签: jsf dynamic menu primefaces

我正在尝试创建一个动态菜单:类似亚马逊或eBay上的菜单来浏览类别。我的第一次尝试如下所示:

支持bean:

@ManagedBean
@ViewScoped
public class CategoryBackBean implements ActionListener {
    private MenuModel model;
    private Category category;

    public CategoryBackBean() throws IOException {
        category = Category.createRootCategory();
        createModel();
    }


    private void createModel() throws IOException {
        MenuModel tempModel = new DefaultMenuModel();
        for(Category c : category.getChildCategories()) {
            MenuItem childItem = new MenuItem();
            childItem.setValue(c.getName());
            childItem.addActionListener(this);
            tempModel.addMenuItem(childItem);
        }
        this.model = tempModel;
    }

    public MenuModel getModel() {
        return model;
    }

    @Override
    public void processAction(ActionEvent event) throws AbortProcessingException {
        try {
            MenuItem item = (MenuItem) event.getSource();
            String categoryName = (String) item.getValue();
            for(Category c : category.getChildCategories()) {
                if(c.getName().equals(categoryName)) {
                    category = c;
                    createModel();
                    return;
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(CategoryBackBean.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

网页:

<h:body>
    <h:form>
        <p:menubar model="#{categoryBackBean.model}" />
    </h:form>
</h:body>

对于初学者,我的设计不起作用:创建了初始菜单,但是当点击按钮时,菜单不会在子类别中重新创建。

解决这个一般问题的最佳方式是什么?我不是在寻找快速的黑客来让上面的代码工作 - 我正在寻找一个递归菜单的通用设计。

4 个答案:

答案 0 :(得分:5)

您需要在操作完成后更新菜单。鉴于您不想对菜单ID进行硬编码,请使用@parent

childItem.setUpdate("@parent");

另一种选择,如果菜单是表单中的唯一组件,则只使用@form

这一切是否是“最佳”方式都无法客观回答。如果代码以最简单和最少侵入的方式完成您想要的工作,那么它是可以接受的。

答案 1 :(得分:0)

解决方案1。

我认为要通知actionListener,首先必须有一个动作事件。所以我尝试添加这个丑陋的代码(当然有更好的方法吗?)为每个childItem添加一个动作事件:

private void createModel() throws IOException {
    FacesContext facesCtx = FacesContext.getCurrentInstance();
    ELContext elCtx = facesCtx.getELContext();
    ExpressionFactory expFact = facesCtx.getApplication().getExpressionFactory();

    MenuModel tempModel = new DefaultMenuModel();
    for(Category c : childCategories) {
        MenuItem childItem = new MenuItem();
        childItem.setValue(c.getName());
        childItem.addActionListener(this);
        childItem.setActionExpression(expFact.createMethodExpression(elCtx, "#{categoryBackBean.doSomething()}", void.class, new Class[0]));
        tempModel.addMenuItem(childItem);
    }
    this.model = tempModel;
}

其中doSomething()只是一个虚方法,以便调用动作监听器。

这导致this issue在提交响应后创建会话,这需要这个hack:

@PostConstruct
public void sessionStart() {
    FacesContext.getCurrentInstance().getExternalContext().getSession(true);
}

毕竟,我需要在每个元素上禁用Ajax,以便提交表单:

childItem.setAjax(false);

总而言之,一个黑客链,但现在它起作用(没有ajax)。

答案 2 :(得分:0)

另一个解决方案,解决方案2.此解决方案避免了设置动作表达式的需要,但是,它确实引入了对XHTML代码的向后依赖性(在这种情况下,<p:menubar ..>必须具有“menuid”的id)

private void createModel() throws IOException {
    MenuModel tempModel = new DefaultMenuModel();
    for(Category c : childCategories) {
        MenuItem childItem = new MenuItem();
        childItem.setValue(c.getName());
        childItem.addActionListener(this);
        childItem.setUpdate("menuId");     // Magic new line.
        tempModel.addMenuItem(childItem);
    }
    this.model = tempModel;
}

这可以通过使每个菜单项通过Ajax更新整个菜单栏来实现。如果有一些方法可以获得菜单的ID而无需硬编码......

答案 3 :(得分:0)

解决方案3.此解决方案非常简单。支持bean:

@ManagedBean 
@ViewScoped 
public class CategoryBackBean implements Serializable {
    private Category category = Category.createRootCategory();
        public void setCategory(Category category) {
        this.category = category;
    }     

    public Category getCategory() { 
        return category;
  }
}

网页:

<h:form>
    <p:menu id="myMenu">
        <c:forEach items="#{categoryBackBean.category.childCategories}" var="subCategory">
            <p:menuitem value="#{subCategory.name}" update="myMenu">
                <f:setPropertyActionListener target="#{categoryBackBean.category}" value="#{subCategory}" />
            </p:menuitem>
        </c:forEach>
    </p:menu>
</h:form>

值得注意的要点:

  • <c:forEach>代替<ui:repeat>,因为后者在Primefaces菜单中不起作用。

  • update="myMenu"在XHTML中设置,而不是在支持代码中。这解决了我的解决方案#2的问题。

  • 设计与建议的Primefaces设计启发式相反,暗示为here,即应使用支持模型而不是使用<ui:repeate><c:forEach>创建菜单(鉴于技术的简单性<c:forEach>),我会反对这一点。

<强>更新

不要使用这个答案! <c:forEach/>'在创建组件树之前运行',并且在页面更新时不会通过Ajax或使用回发重新运行。它对我有用的唯一原因是由于第一类具有最多的子类别,因此,它最初将为任何其他类别创建具有足够<p:menuitems />的组件树。