【问题标题】:How to implement a recursive menu with PrimeFaces如何使用 PrimeFaces 实现递归菜单
【发布时间】:2012-11-29 05:16:10
【问题描述】:

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

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

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

【问题讨论】:

  • 您应该在@SessionScoped 托管bean 中动态构建您的菜单组件,然后将此菜单绑定到您的&lt;p:menubar&gt;。动态创建 PrimeFaces 组件:How to create Dynamic PrimeFaces menus
  • 大声笑,这是我自己的博客。我对那个设计有意见。我正在尝试远离@SessionScoped,并想使用ajax(您链接的设计没有)。
  • 我喜欢那个设计。您能否粘贴类别类或接口的内容。谢谢
  • 接口将只包含您在此处看到的方法。这将完全取决于上下文。有关模式的详细信息,请参见此处:kevindoran1.blogspot.co.nz/2012/02/…

标签: jsf dynamic menu primefaces


【解决方案1】:

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

childItem.setUpdate("@parent");

如果菜单是表单中的唯一组件,另一种方法是使用@form

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

【讨论】:

    【解决方案2】:

    解决方案 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;
    }
    

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

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

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

    在所有这些之后,我需要在每个元素上禁用 Ajax 以便提交表单:

    childItem.setAjax(false);
    

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

    【讨论】:

    • 禁用 Ajax 的替代方法是向 childItem 添加 Javascript 操作:childItem.setOnclick("document.forms[0].submit()");
    【解决方案3】:

    另一个解决方案,解决方案 2。此解决方案避免了设置操作表达式的需要,但是,它确实引入了对 XHTML 代码的向后依赖(在这种情况下,&lt;p:menubar ..&gt; 必须有一个 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...

    【讨论】:

      【解决方案4】:

      解决方案 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>
      

      值得注意的点:

      • &lt;c:forEach&gt; 被用来代替 &lt;ui:repeat&gt;,因为后者在 Primefaces 菜单中不起作用。

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

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

      更新

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

      【讨论】:

      • 我不明白你到底想要什么。我的应用程序中有一个p:menu,它以编程方式加载每个操作,我在上面使用了c:forEach 标签。由于我的菜单适用于非 Ajax 调用,我对此没有任何问题。 c:forEach 确实在 JSF 执行之前执行,但如果您将它附加到 @SessionScoped 属性上且其上没有 Ajax,则没有问题。
      猜你喜欢
      • 1970-01-01
      • 2020-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-14
      • 2011-06-16
      • 1970-01-01
      • 2019-11-16
      相关资源
      最近更新 更多