Reputation: 11
I try to use p:tree in PrimeFaces 4.0 in dynamic mode together with persistence API. I have @Entity class (Nomen) which already have elements referencing itself. I tried both variants: implement in that very Nomen class interface TreeNode and make it a subclass of DefaultTreeNode. The last variant extends DefaultTreeNode. Behaviour is the same.
@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 file is quite simple like in PrimeFaces sample:
<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>
The strange unexplainable behaviour of p:tree is that it renders normally (with Nomen.toString) only the second (last) child (with id=2) of root node. It renders the whole tree structure which is expandable further deep with icons, but the labels are empty.
Below is server protocol extracted when loading initial view of the tree where two children (with id 1 and 2) of root (id=2159) are expected to be rendered:
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 ] (Материалы)] ]]
The sequence of calls seems strange.
There is no call of getData for line 5 of log (expected call getData then getRowKey as in lines 9,11)
Too many calls of getChildren for ROOT (expected call of getChildren and then iterate through List with subsequent getData calls.
I can't understand what is wrong. Tracing getChildren calls gives correct tree structure even further deep. Collections are traced to be instantiated. (Adding FetchType.EAGER changed nothing). I plan to use the p:tree component with drag&drop to implement Hierarcy Editor but now stop at this point. I would not like to include Nomen as Data Object in DefaultTreeNode and then duplicate there natural tree structure already implemented by entity class itself. And then syncing both parent changes ;-)
Can anybody help?
Upvotes: 0
Views: 1583
Reputation: 11
Digging the sources of DefaultTreeNode discovers some subtle featuers poorly reflected in docs available.
Fisrt of all I mean special (I think general as the matter) use case: I have @Entity class which already contains elements referencing itself. I try to implement hierarcy editor for that class. Somebody in The Forum years ago suggests subclass DefaultTreeNode, I decided do the same. Some essential things are missed in docs and I have had to research the real beahviour of the p:tree and TreeNode. Here I try to decscribe that subtle features If somebody need.
In my case (PF4.0, JSF2.2, Netbeans8.0, Glassfish4.0) it was insufficient to add draggable attr for p:tree. I have to add p:draggable for "thattree" and 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"/>
Line
<p:ajax event="dragdrop" ...
in previous sample is nesessary to setup listener. p:tree doesn't call model's setParent method on dragdropevent that is why we need listener to adjust tree structure in backing bean after nodes are reparented in client view. In that listener we simply call model's setParent. Have to note that in docs for PF4.0 "dragdrop" event is not listed among ajax events for p:tree.
The fact is that DefaultTreeNode which is recommended to subclass does nothing just getters and setters, which implement TreeNode interface. The first trick is in constructors: all constructors in DefaultTreeNode initialize member List children with new TreeNodeChildren (extends List). At the very time DefaultTreeNode has public not final method setChildren(List) which is trivial setter and allows to set any List.
And at the very time TreeNodeChildren extends List overriding every method which adds or removes items adding to everyone of them call to private updateRowKeys. That very updateRowKeys recalculate rowkeys of the whole subtree every time tree structure changes and stores that calculated keys using DefaultTreeNode::setRowKey. (what a mess!). These rowkeys have the form: "0_1_1", reflecting the hierarchy. And they are used (somehow) to render tree. If structure of rowkeys is different p:tree doesn't render node labels correctly (just empty labels).
Thus, to cause p:tree work with subclass of DefaultTreeNode or with class which implements TreeNode you have to either override getRowKey() or implement that algorithm with updateRowKeys. This is the second trick not documented. I prefer to override getRowKey() to support that digged key structure "0_1_1" in AbstractTreeNode 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;
}
}
Here getType(), getParent(), setParent() and getChildren() are pure abstract to allow subclass reflect its own concrete tree structure and that very getRowKey() is final since it can't be different (? to further dig ;-)
Than it can be used like this:
@Entity
public class SomeTable extends AbstractTreeNode<SomeTable> implements Serializable {
...
Sorry for my poor english. Hope this will help somebody to avoid long digging.
Upvotes: 0
Reputation: 11
The problem is solved by commenting out the following code
/*
@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());
}
*/
I certainly did not know how getRowKey is used by p:tree. Likely, it have to correspond to datakey attribute of p:tree somehow. I haven't dig in documentation how they should work together. BUT all that mess in call sequence was caused by overriding getRowKey().
Thanks everybody for reading!
Upvotes: 1