Jsf 如何使用PrimeFaces实现递归菜单

Jsf 如何使用PrimeFaces实现递归菜单,jsf,dynamic,menu,primefaces,Jsf,Dynamic,Menu,Primefaces,我正在尝试创建一个动态菜单:一个类似于亚马逊或易趣上的菜单,用于浏览类别。我的第一次尝试如下所示: 支持bean: @ManagedBean @ViewScoped public class CategoryBackBean implements ActionListener { private MenuModel model; private Category category; public CategoryBackBean() throws IOException

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

支持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);
        }
    }
}
@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:body>
    <h:form>
        <p:menubar model="#{categoryBackBean.model}" />
    </h:form>
</h:body>

首先,我的设计不起作用:创建初始菜单,但单击按钮时,菜单不会在子类别中重新创建

解决这个普遍问题的最佳方法是什么?我不是在寻找能让上述代码正常工作的快速破解——我是在寻找递归菜单的通用设计

解决方案1

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

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()
只是一个伪方法,用于调用动作侦听器

这导致在提交响应后创建会话,这需要进行以下攻击:

@PostConstruct
public void sessionStart() {
    FacesContext.getCurrentInstance().getExternalContext().getSession(true);
}
在所有这些之后,我需要在每个元素上禁用Ajax,以便提交表单:

childItem.setAjax(false);

总之,这是一个黑客链,但它现在可以正常工作(没有ajax)。

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


这是通过使每个菜单项通过Ajax更新整个菜单栏来实现的。如果没有硬编码就可以获取菜单id…

解决方案3。这个解决方案非常简单。支持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);
        }
    }
}
@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>

值得注意的是:

  • 使用了
    而不是
    ,因为后者在Primefaces菜单中不起作用

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

  • 正如所暗示的,该设计违背了Primefaces设计启发法的建议,即应使用支持模型,而不是使用
    创建菜单(鉴于该技术的简单性,我会反对这一点)
更新


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

操作完成后,您需要更新菜单。如果您不想硬编码菜单ID,请使用
@parent

childItem.setUpdate("@parent");
如果菜单是表单中唯一的组件,另一种选择是只使用
@form


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

您应该在
@SessionScoped
托管bean中动态构建菜单组件,然后将此菜单绑定到
。动态创建PrimeFaces组件:哈哈,这是我自己的博客。我对那个设计有意见。我正试图摆脱
@SessionScoped
,并希望使用ajax(您链接的设计没有使用ajax)。我喜欢这种设计。请粘贴类别类或接口的内容。谢谢界面将只包括您在这里看到的方法。这完全取决于上下文。查看此处了解模式详细信息:禁用Ajax的另一种方法是向childItem添加Javascript操作:
childItem.setOnclick(“document.forms[0].submit()”p:menu
,它以编程方式加载每个操作,我在上面使用了
c:forEach
标记。由于我的菜单适用于非Ajax调用,因此我对此没有问题。确实,
c:forEach
在JSF中没有任何其他内容之前执行,但是如果将它附加到
@SessionScoped
属性上,并且在该属性上附加一个非Ajax属性,则没有问题。