使用DefaultTreeNode子类的p:treenode的奇怪行为

时间:2014-08-12 13:25:57

标签: primefaces treenode

我尝试在动态模式下使用PrimeFaces 4.0中的p:tree和persistence API。 我有@Entity类(Nomen),它已经有引用自身的元素。我尝试了两种变体:在非常Nomen类接口TreeNode中实现并使其成为DefaultTreeNode的子类。最后一个变体扩展了DefaultTreeNode。行为是一样的。

@Entity
@Table(name = "Nomen")
public class Nomen extends DefaultTreeNode implements Serializable {
@Id
private Integer id;
...
@Column(name = "Name")
private String name;
...
@JoinColumn(name = "Self_Id", referencedColumnName = "Id")
@ManyToOne
private Nomen parent;

@OneToMany(mappedBy = "parent", fetch=FetchType.EAGER)
private List<Nomen> nomenCollection;
...
public Nomen() {
    super();
}
...
@Override
public String getType() {
    if (nomenCollection == null) return "NomenLeaf";
    return "NomenGroup";
}

@Override
public Nomen getData() {
    Logger.getLogger(this.getClass().getName()).log(Level.INFO, "getData for {0}", new Object[] {this});
    return this;
}


@Override
public Nomen getParent() {
    return this.parent;
}
@Override
public void setParent(TreeNode tn) {
    if (!(tn instanceof Nomen)) {
        return;
    }
    try {
        parent.removeChild(this);
    }
    catch (NullPointerException e) { }// Nothing to do
    if (tn != null) {
        ((Nomen)tn).addChild(this);
    }
    parent = (Nomen)tn;
}

@Override
public int getChildCount() {
    if (nomenCollection == null) return 0;
    return nomenCollection.size();
}

@Override
public boolean isLeaf() {
    return nomenCollection == null;
}

@Override
public String getRowKey() {
    Logger.getLogger(this.getClass().getName()).log(Level.INFO, "getRowKey: this: {0} id: {1}", new Object[] {this, Integer.toString(getId())} );
    return Integer.toString(getId());
}
@Override
public List<TreeNode> getChildren() {
//       return new ArrayList<TreeNode> (nomenCollection);
//       return (List<TreeNode>)(List<? extends TreeNode>)nomenCollection;
    ArrayList<TreeNode> r = new ArrayList<> ();
    for (Nomen n : nomenCollection) {
        r.add(n);
    }
    Logger.getLogger(this.getClass().getName()).log(Level.INFO, "getChildren for {0}: List: {1} ", new Object[] {this, r});
    return r;
}
public List<Nomen> getNomenCollection() {
    return nomenCollection;
}
public void setNomenCollection (List<Nomen> n) {
    nomenCollection = n;
}
}

XHTML文件非常简单,就像在PrimeFaces示例中一样:

<h:form id="myform">
    <p:tree id="ntree" value="#{nomenPfCtl.root}" var="item" selectionMode="single" selection="#{nomenPfCtl.selected}" datakey="id" dynamic="true">
        <p:treeNode type="NomenGroup" expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
            <h:outputText value="#{item}"/>
        </p:treeNode>
        <p:treeNode type="NomenLeaf" expandedIcon="ui-icon-document" collapsedIcon="ui-icon-document">
            <h:outputText value="#{item}"/>
        </p:treeNode>
    </p:tree>
</h:form>

p:tree的奇怪的无法解释的行为是它通常(使用Nomen.toString)呈现根节点的第二个(最后一个)子节点(具有id = 2)。它呈现整个树形结构,可以用图标进一步展开,但标签是空的。

下面是加载树的初始视图时提取的服务器协议,其中预计将呈现两个root(id = 2159)的子节点(id = 2159):

  1.getRoot: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура)]]
  2.getRowKey: this: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура) id: 2159]]
  3.getRowKey: this: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура) id: 2159]]
  4.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  5.getRowKey: this: AsupoksEntities.Nomen[ id=1 ] (Работы) id: 1]]
  6.getRoot: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура)]]
  7.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  8.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  9.getData for AsupoksEntities.Nomen[ id=2 ] (Материалы)]]
  10.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  11.getRowKey: this: AsupoksEntities.Nomen[ id=2 ] (Материалы) id: 2]]
  12.getRoot: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура)]]
  13.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  14.getRoot: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура)]]
  15.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  16.getRoot: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура)]]
  17.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  18.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  19.getData for AsupoksEntities.Nomen[ id=1 ] (Работы)]]
  20.getRoot: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура)]]
  21.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  22.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]
  23.getData for AsupoksEntities.Nomen[ id=2 ] (Материалы)]]
  24.getRoot: AsupoksEntities.Nomen[ id=2159 ] (Номенклатура)]]
  25.getChildren for AsupoksEntities.Nomen[ id=2159 ] (Номенклатура): List: [AsupoksEntities.Nomen[ id=1 ] (Работы), AsupoksEntities.Nomen[ id=2 ] (Материалы)] ]]

电话的顺序似乎很奇怪。

  1. 没有为日志第5行调用getData(预期调用getData,然后调用getRowKey,如第9,11行所示)

  2. 对ROOT的getChildren调用太多(预期调用getChildren,然后使用后续的getData调用迭代List。

  3. 我无法理解什么是错的。跟踪getChildren调用可以更深入地提供正确的树结构。跟踪集合以进行实例化。 (添加FetchType.EAGER没有改变任何内容)。我计划使用拖放的p:tree组件来实现Hierarcy Editor,但现在停止了。我不希望在DefaultTreeNode中包含Nomen作为数据对象,然后复制已经由实体类本身实现的自然树结构。然后同步两个父更改; - )

    有人可以帮忙吗?

2 个答案:

答案 0 :(得分:1)

通过注释掉以下代码来解决问题

/*
@Override
public String getRowKey() {
    Logger.getLogger(this.getClass().getName()).log(Level.INFO, "getRowKey: this: {0} id: {1}", new Object[] {this, Integer.toString(getId())} );
    return Integer.toString(getId());
}
*/

我当然不知道p:tree如何使用getRowKey。可能,它必须以某种方式对应于p:tree的datakey属性。我没有在文档中挖掘它们应该如何协同工作。但是,调用序列中的所有混乱都是由覆盖getRowKey()引起的。

感谢大家的阅读!

答案 1 :(得分:0)

挖掘DefaultTreeNode的来源发现了一些在可用文档中反映不佳的微妙特征。

Fisrt of my I mean special(我认为一般的事情)用例:我有@Entity类,它已经包含引用自身的元素。我尝试为该课程实现hierarcy编辑器。几年前论坛中有人建议使用子类DefaultTreeNode,我决定这样做。在文档中遗漏了一些基本的东西,我不得不研究p:tree和TreeNode的真正的beahviour。在这里,我试着去解决那些微妙的特征。如果有人需要的话。

  1. 在我的情况下(PF4.0,JSF2.2,Netbeans8.0,Glassfish4.0),为p:tree添加可拖动的attr是不够的。我必须补充一下 p:可拖动“thattree”和p:droppable

    <p:tree id="ntree" value="#{treeController.root}" var="nod" draggable="true" droppable="true" dynamic="true">
        <p:ajax event="dragdrop" listener="#{treeController.onDragDrop}" update="@this">
    ...
    </p:tree>
    <p:draggable for="ntree" handle=".ui-treenode-label, .ui-treenode-icon"/>
    <p:droppable for="ntree"/>
    
  2. <p:ajax event="dragdrop" ... 
    
  3. 上一个示例中的

    是设置侦听器的必要条件。 p:tree不会在dragdropevent上调用模型的setParent方法,这就是为什么我们需要监听器在客户端视图中重构节点后调整辅助bean中的树结构。 在那个监听器中,我们只需调用model的setParent。必须注意的是,在PF4.0的文档中,“dragdrop”事件未列在p:tree的ajax事件中。

    1. 如何继承DefalutTreeNode。文档中遗漏了一个非常微妙的特征,只挖掘源代码有助于它的工作。
    2. 事实是,推荐给子类的DefaultTreeNode不会只执行实现TreeNode接口的getter和setter。第一个技巧是在构造函数中:DefaultTreeNode中的所有构造函数使用新的TreeNodeChildren(扩展List)初始化成员List子代。在DefaultTreeNode具有public not final方法setChildren(List)的时候,这是一个简单的setter并允许设置任何List。

      当时TreeNodeChildren扩展了List覆盖每个添加或删除项目的方法,这些方法增加了对每个人的调用私有updateRowKeys。每次树结构更改时,updateRowKeys都会重新计算整个子树的rowkeys,并使用DefaultTreeNode :: setRowKey存储计算出的密钥。 (真是一团糟!)。这些行键的格式为“0_1_1”,反映了层次结构。并且它们(以某种方式)用于渲染树。如果rowkeys的结构不同p:tree不能正确呈现节点标签(只是空标签)。

      因此,要使p:tree使用DefaultTreeNode的子类或使用实现TreeNode的类,您必须覆盖getRowKey()或使用updateRowKeys实现该算法。这是没有记录的第二个技巧。我更喜欢覆盖getRowKey()以支持AbstractTreeNode中的挖掘键结构“0_1_1”extrends DefaultTreeNode:

      public abstract class AbstractTreeNode<T> extends DefaultTreeNode {
      
          @Override
          public abstract String getType();
      
          @Override
          public abstract AbstractTreeNode<T> getParent();
      
          @Override
          public abstract void setParent(TreeNode tn);
      
          @Override
          public abstract List<TreeNode> getChildren();
      
          public AbstractTreeNode() { super(); }
      
          @Override
          public T getData() {
              return (T)this;
          }
      
          @Override
          public int getChildCount() {
              if (getChildren() == null) return 0;
              return getChildren().size();
          }
      
          @Override
          public boolean isLeaf() {
              return (getChildren() == null) || getChildren().isEmpty();
          }
      
          @Override
          public final String getRowKey() {
              String r;
              if (getParent() == null) r = "";
              else if (getParent().getParent() == null)
                  r = ""+getParent().getChildren().indexOf(this);
              else {
                  r = getParent().getRowKey() + "_" + getParent().getChildren().indexOf(this);
              }
              return r;
          }
      }
      

      这里getType(),getParent(),setParent()和getChildren()是纯抽象的,允许子类反映自己的具体树结构,而且getRowKey()是最终的,因为它不能不同(?进一步挖; - )

      可以像这样使用:

      @Entity
      public class SomeTable extends AbstractTreeNode<SomeTable> implements Serializable {
      ...
      

      抱歉我的英语不好。希望这能帮助有人避免长时间挖掘。