カスタムツリービューの基本的な使い方(その10~階層構造 – フォルダ開閉)

その9~階層構造 – 表示」で作成したツリーは表示のみであったが、今回フォルダの開閉機能を実装する。

フォルダの開閉機能

フォルダの行をダブルクリックしたり、フォルダ上でEnterキーを押下したりすると、 nsITreeView#toggleOpenState メソッドが呼び出される。
toggleOpenState では、 _visibleData の中の引数 index に対応するアイテムの open プロパティを変更し、 _buildVisibleData を使って _visibleData を再構築する。
さらに、フォルダ開閉に伴い行数に変化が生じたため、 nsITreeBoxObject#rowCountChanged を呼び出す必要がある。
rowCountChanged の第1引数は変化が生じた最初の行番号、第2引数は行数の増減値である。
例えば0行目の「Red」フォルダを閉じると、そのフォルダのすぐ下の4行が消滅するため、 rowCountChanged(1, -4) となる。
これでめでたく完了、と思いきやフォルダ上でEnterキーを押した場合にフォルダの開閉状態を示す +/- 記号に変化が無いという問題があった。
そこで、 nsITreeBoxObject#invalidateRow によってその行だけを再描画する必要がある。

    toggleOpenState: function(index) {
        var lastRowCount = this.rowCount;
        // change |open| property
        this._visibleData[index].open = !this._visibleData[index].open;
        this._buildVisibleData();
        this._treeBoxObject.rowCountChanged(index + 1, this.rowCount - lastRowCount);
        // need this to update the -/+ sign when called by pressing enter key
        this._treeBoxObject.invalidateRow(index);
    },

例えば「Yellow」フォルダをダブルクリックして開いたとすると、再構築された _visibleData は下表に示すような配列となる。
item#2 の水色で着色した箇所が、フォルダを開いた際に変更された open プロパティである。
また、緑色で着色した item#9 と item#C が、フォルダを開いたことによって新たに追加されたアイテムである。

id type name parent open empty level hasNext parentIndex
[0] item#1 2 Red root true false 0 true -1
[1] item#5 1 Apple item#1 1 true 0
[2] item#6 1 Cherry item#1 1 true 0
[3] item#7 3 item#1 1 true 0
[4] item#8 1 Peach item#1 1 false 0
[5] item#2 2 Yellow root true false 0 true -1
[6] item#9 2 Citrus item#2 false false 1 true 5
[7] item#C 1 Banana item#2 1 false 5
[8] item#3 3 root 0 true -1
[9] item#4 2 Blue root false true 0 false -1

応用例~シングルクリックでのフォルダの開閉~

上記で実装したように、通常フォルダはダブルクリック時にフォルダの開閉が可能だが、ブックマークツリーのようにシングルクリックでもフォルダも開閉を可能にする。
まず、 fruits.xul の tree または treechildren 要素へ onclick 属性を追加する。

    <tree id="fruitsTree" flex="1" onclick="handleClick(event);">

先ほど onclick 属性で追加したイベントハンドラである handleTreeClick 関数を実装する。
その際、クリックした位置のアイテムを取得するために nsITreeBoxObject#getCellAt を使ってヒットテストを行う。
nsITreeBoxObjcet#getCellAt メソッドは、第1引数、第2引数で指定した座標にセルがあるかを判定し、セルがある場合は第3引数、第4引数、第5引数に引き渡したオブジェクトの value プロパティにそれぞれ行番号、列を表す nsITreeColumn オブジェクト、セル内の部位を表す文字列(””, “cell”, “text”, “image”, “twisty” のうちのいずれか)がセットされる。
今回は第1引数、第2引数にはクリックした時のマウスポインタ位置を渡して、返ってきた第3引数、第5引数の value プロパティを調べ、ツリーカラムやツリー内の余白部分などの非セル部分をクリックした場合 (row.value == -1)、フォルダ左端の+/-記号をクリックした場合 (obj.value == twisty”) を除外する。クリックした位置がツリーのセルであり、なおかつその行がフォルダである場合のみ、 nsITreeView#toggleOpenState でフォルダの開閉を行う。

////////////////////////////////////////////////////////////////
// Event Handlers

function handleClick(event) {
    if (event.button != 0)
        return;
    // hit test
    var row = {}, obj = {};
    gFruitsTreeView._treeBoxObject.getCellAt(event.clientX, event.clientY, row, {}, obj);
    if (row.value == -1 || obj.value == "twisty")
        return;
    if (gFruitsTreeView.isContainer(row.value))
        gFruitsTreeView.toggleOpenState(row.value);
}

関連記事

TOP

TOP