3

我正在尝试创建一个动态菜单:类似于在亚马逊或 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

4 回答 4

5

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

childItem.setUpdate("@parent");

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

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

于 2013-01-12T01:03:57.347 回答
0

解决方案 1。

我认为要通知 actionListener,首先必须有一个 action 事件。所以我尝试添加这个丑陋的代码(肯定有更好的方法吗?)为每个 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;
}

WheredoSomething()只是一个用于调用动作侦听器的虚拟方法。

这导致了在提交响应后创建会话的问题,这需要这个 hack:

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

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

childItem.setAjax(false);

所以总而言之,一个hack链,但它现在可以运行(没有ajax)。

于 2012-11-29T22:31:12.050 回答
0

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

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...

于 2012-11-30T04:08:48.880 回答
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 菜单中不起作用。

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

  • 该设计违背了建议的 Primefaces 设计启发式,正如这里所暗示的那样,即应该使用支持模型而不是使用<ui:repeate>or创建菜单<c:forEach>(鉴于技术的简单性,我会反对这一点<c:forEach>)。

更新

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

于 2012-12-01T00:44:32.573 回答