かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

PrimeFacesのtreeコントロールで遅延読み込み

最近遅延読み込みがマイブームです。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にすることでツリーを展開するたびにサーバーから最新データをとってくるようにしています。実行したら、こんな感じのいい感じのツリーになります。