如何使用PrimeFaces实现递归菜单



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

支持豆:

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

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

解决此一般问题的最佳方法是什么?我不是在寻找让上面的代码工作的快速技巧——我是在寻找递归菜单的通用设计。

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

childItem.setUpdate("@parent");

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

这一切是否是"最好"的方式还不能客观地回答。如果代码以最简单、干扰最小的方式完成了您想要的工作,那么它是可以接受的。

解决方案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代码的向后依赖(在这种情况下,<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。这个解决方案非常简单。支持豆:

@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设计启发法,正如这里所暗示的那样,即应该使用支持模型,而不是使用<ui:repeate><c:forEach>创建菜单(鉴于技术<c:forEach>的简单性,我反对这一点)

更新

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

最新更新