最近遅延読み込みがマイブームです。PrimeFacesのtreeコントロールは、遅延読み込み機能とかいうのは明示的にあったりはしないんですよね。dataTableの方にはあるのに。
ということで、treeのほうはTreeNodeを自前実装してgetChildCountやisLeafやgetChildrenメソッドで遅延読み込みするようなものを作ってやればOKです。とりあえず、DefaultTreeNodeクラスを継承して以下のようなものを作ってみました。
package com.mycompany.primesample.tree; import java.util.List; import org.primefaces.model.DefaultTreeNode; import org.primefaces.model.TreeNode; public class LazyTreeNode extends DefaultTreeNode { private static final long serialVersionUID = 1L; private boolean loaded; private LazyTreeNodeHandler handler; public LazyTreeNode() { super(); } public LazyTreeNode(Object data, TreeNode parent) { super(data, parent); } public LazyTreeNode(String type, Object data, TreeNode parent) { super(type, data, parent); } public static interface LazyTreeNodeHandler { int getChildCount(Object parent); List getChildren(Object parent); } @Override public boolean isLeaf() { return this.getChildCount() == 0; } @Override public int getChildCount() { return this.handler.getChildCount(this.getData()); } @Override public List<TreeNode> getChildren() { if (this.loaded) { return super.getChildren(); } this.loaded = true; List l = this.handler.getChildren(this.getData()); for (Object item : l) { LazyTreeNode child = new LazyTreeNode(item, this); child.setHandler(this.getHandler()); } return super.getChildren(); } public LazyTreeNodeHandler getHandler() { return handler; } public void setHandler(LazyTreeNodeHandler handler) { this.handler = handler; } public void reset() { this.loaded = false; super.getChildren().clear(); } }
遅延読み込みのための処理をLazyTreeNodeHandlerインターフェースというオレオレインターフェースに委譲する感じです。DefaultTreeNodeが親をセットすると気を利かせて親の子供のコレクションに自動で追加してくれるので、そこでスタックオーバーフローにならないように読み込み済かどうかのフラグを立てて無限に読み込み続けることにならないようにするのがはまりどころです。
しっかりしたのを作るならTreeNodeインターフェースを実装したほうがいいかもしれません・・・。
使い方
とりあえずテスト用にこんな管理ビーンを用意しました。
package com.mycompany.primesample; import com.mycompany.primesample.tree.LazyTreeNode; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; import org.primefaces.model.DefaultTreeNode; @ManagedBean @ViewScoped public class LazyTreePage implements Serializable { private DefaultTreeNode root; @PostConstruct public void init() { root = new DefaultTreeNode(); LazyTreeNode.LazyTreeNodeHandler h = new LazyTreeNode.LazyTreeNodeHandler() { private final Random R = new Random(); @Override public int getChildCount(Object parent) { System.out.println(((Person)parent).getId() + "#getChildCount called."); return 10; } @Override public List getChildren(Object parent) { System.out.println(((Person)parent).getId() + "#getChildren called."); Person p = (Person) parent; p.setChildren(new ArrayList<Person>()); for (int i = 0; i < 10; i++) { Person child = new Person(); child.setId(p.getId() + R.nextInt()); child.setName(p.getName() + "の子供"); p.getChildren().add(child); } return p.getChildren(); } }; Person p = new Person(); p.setId(100); p.setName("田中 太郎"); LazyTreeNode n1 = new LazyTreeNode(p, root); n1.setHandler(h); } public DefaultTreeNode getRoot() { return root; } public void setRoot(DefaultTreeNode root) { this.root = root; } }
ルートノードに田中太郎を作ってLazyTreeNodeに渡してます。LazyTreeNodeHandlerの実装は、といあえず子供が必ず10人いるという実装にしてあります。
そしてxhtmlでtreeコントロールに紐づけます。
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"> <h:head> <title>Lazy tree</title> </h:head> <h:body> <h:form> <p:tree var="p" value="#{lazyTreePage.root}" dynamic="true" cache="false"> <p:treeNode> <h:outputText value="#{p.id} #{p.name}" /> </p:treeNode> </p:tree> </h:form> </h:body> </html>
dynamicをtrueにしておくことで動的に読み込むようにして、cacheをfalseにすることでツリーを展開するたびにサーバーから最新データをとってくるようにしています。実行したら、こんな感じのいい感じのツリーになります。