かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

RichFaces 4.xのtreeコンポーネントで遅延読み込み

でっかいツリーを表示したいが、最初に全部データをクライアントに持っていくのは忍びない。というか普通にある程度の規模以上になると性能問題になりますよね。
そんなときのために、表示するのに必要最低限のデータだけをとっておいて、あとは必要に応じて取得するという手は常套手段です。やってみましょう。

表示するデータ

表示するデータは、実際にDBにアクセスするものはめんどくさかったので以下のような簡易的なものです。

package okazuki.mavenproject4;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class Item {

    private static final Random RANDOM = new Random();
    
    private String text;
    private List<Item> items;
    private boolean leaf;
    
    public Item() {
    }

    public Item(String text) {
        this.text = text;
        // 自分が葉なのか枝なのかは、最初に取得しておく
        this.leaf = RANDOM.nextBoolean();
    }

    public String getText() {
        return text;
    }

    // 必要になるまで子は生成しない
    public List<Item> getItems() {
        if (leaf) {
            return Collections.EMPTY_LIST;
        }
        
        if (items == null) {
            this.items = new LinkedList<>();
            int childCount = RANDOM.nextInt(10) + 1;
            for (int i = 0; i < childCount; i++) {
                this.items.add(new Item(text + "-" + i));
            }
        }
        return items;
    }

    // 自分が葉なのかどうか返す
    public boolean isLeaf() {
        return this.leaf;
    }

    @Override
    public String toString() {
        return "Item{" + "text=" + text + '}';
    }
}

ランダムで枝になったり葉になったり。恐らく延々とItemsプロパティをたどっていくと無限に階層が深くなっていくツリーになると思います。

管理ビーン作成

さて、管理ビーンです。こいつはViewScopeで画面遷移するまでメモリ上に残ってもらうようにしました。RequestScopeにしたら、表示してるものは、全部再構築しないといけないのかな。そこらへんちょっとよくわからない。

package okazuki.mavenproject4;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

/**
 *
 * @author Kazuki
 */
@ManagedBean
@ViewScoped
public class IndexPage implements Serializable {

    List<Item> items;

    @PostConstruct
    public void init() {
        // ルートノードだけ最初に組み立てる
        items = Arrays.asList(
                new Item("item1"),
                new Item("item2"),
                new Item("item3"),
                new Item("item4"),
                new Item("item5"),
                new Item("item6"),
                new Item("item7"),
                new Item("item8"));
    }

    public List<Item> getRoots() {
        return items;
    }
}

とりあえず、最初に8個のルートノードを作っておきます。

画面作成

ここから画面作成です。treeコンポーネントは、toggleTypeというのでajaxを指定すると、いい感じに裏でajaxで必要なだけデータをとってきてくれます。どういう構造のデータをとるのかという点は、treeModelRecursiveAdaptorで指定してます。設定してる属性の意味は、以下のような感じです。

属性名 説明
roots ルートとなるノードをEL式で設定
nodes ノードをたどる方法をEL式で設定。この場合はitemsプロパティでたどるので、item.itemsにしている。
leaf ノードが葉なのかどうかを設定。この場合はleafプロパティで判別できるので、item.leafにしている。

EL式の中で使ってるitemは、treeコンポーネントのvarで指定したものが使えます。型は、今回の場合はItem型になります。

あとは、treeコンポーネントの中にtreeNodeで表示の仕方を設定します。xhtmlは以下のような感じ。

<?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:rich="http://richfaces.org/rich">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form>
            <rich:tree var="item" toggleType="ajax">
                <rich:treeNode>
                    <h:outputText value="#{item.text}" />
                </rich:treeNode>
                <rich:treeModelRecursiveAdaptor roots="#{indexPage.roots}" nodes="#{item.items}" leaf="#{item.leaf}">
                </rich:treeModelRecursiveAdaptor>
            </rich:tree>
        </h:form>
    </h:body>
</html>

実行すると

実行してツリーを適当にマウスを使って展開してみました。さくさくっと動く。

まとめ

RichFacesのtreeコンポーネントを使うことで、割と簡単に遅延読み込みが実装できることがわかりました。この調子でDataTable系も次は調べてみようかな?