我正在尝试创建一个动态菜单:类似亚马逊或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>
对于初学者,我的设计不起作用:创建了初始菜单,但是当点击按钮时,菜单不会在子类别中重新创建。
解决这个一般问题的最佳方式是什么?我不是在寻找快速的黑客来让上面的代码工作 - 我正在寻找一个递归菜单的通用设计。
答案 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的问题。
<ui:repeate>
或<c:forEach>
创建菜单(鉴于技术的简单性<c:forEach>
),我会反对这一点。<强>更新强>
不要使用这个答案! <c:forEach/>
'在创建组件树之前运行',并且在页面更新时不会通过Ajax或使用回发重新运行。它对我有用的唯一原因是由于第一类具有最多的子类别,因此,它最初将为任何其他类别创建具有足够<p:menuitems />
的组件树。