我尝试在动态模式下使用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 ] (Материалы)] ]]
电话的顺序似乎很奇怪。
没有为日志第5行调用getData(预期调用getData,然后调用getRowKey,如第9,11行所示)
对ROOT的getChildren调用太多(预期调用getChildren,然后使用后续的getData调用迭代List。
我无法理解什么是错的。跟踪getChildren调用可以更深入地提供正确的树结构。跟踪集合以进行实例化。 (添加FetchType.EAGER没有改变任何内容)。我计划使用拖放的p:tree组件来实现Hierarcy Editor,但现在停止了。我不希望在DefaultTreeNode中包含Nomen作为数据对象,然后复制已经由实体类本身实现的自然树结构。然后同步两个父更改; - )
有人可以帮忙吗?
答案 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。在这里,我试着去解决那些微妙的特征。如果有人需要的话。
在我的情况下(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"/>
行
<p:ajax event="dragdrop" ...
是设置侦听器的必要条件。 p:tree不会在dragdropevent上调用模型的setParent方法,这就是为什么我们需要监听器在客户端视图中重构节点后调整辅助bean中的树结构。 在那个监听器中,我们只需调用model的setParent。必须注意的是,在PF4.0的文档中,“dragdrop”事件未列在p:tree的ajax事件中。
事实是,推荐给子类的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 {
...
抱歉我的英语不好。希望这能帮助有人避免长时间挖掘。