
ExtJSのエントリは
こちらにまとめてあります
ダウンロードページより、アーカイブ(Ext JS 2.1 SDK)をダウンロードして解凍してください。20080522時点のバージョンは ext-2.1 です。解凍したら、以下のパスのファイルを開いて見ましょう。
【パス】
ext-2.1\examples\tree\reorder.html
開いてみると、ツリーのルートノードが表示されているだけでクリックしても以下のノードが見えませんね。これはサーバサイトスクリプトを参照しに行っていて、その参照先がローカルのget-nodes.phpにあるためです。ですので
直接ExtJSのTreePanelサンプルを見てみます。
このウィジェットがTreePanelと呼ばれるものです。サンプルでは(ローカルパスで言う)ext-2.1\source以下のディレクトリ構造がツリーパネルで表現されていて、ディレクトリをクリックすると以下のノード一覧をロードしてずらっと表示する仕組みになっています。今日はこのTreePanelの基本的な実装を解説してみたいと思いますが、サーバサイドスクリプト抜きで静的なツリー構造をサンプルとして使いたいと思います。あと、いきなりTreePanelとか言われても・・と言う方には
ここにExtJSのチュートリアルを簡単にまとめてみました。
まず、以下に今回解説するTreePanelのソースを全て載せてみましたので、この実装方法とコンフィグを解説します。
動作サンプルとサンプルソース
Ext.onReady( function(){
var idx = 0;
var root = createNode( idx++, "root", false, false ); // branch unDraggable
var node1 = createNode( idx++, "hoge", true, true ); // leaf draggable
var node2 = createNode( idx++, "moge", false, true ); // branch draggable
var node3 = createNode( idx++, "piyo", true, false ); // leaf unDraggable
root.appendChild( node1 );
root.appendChild( node2 );
node2.appendChild( node3 );
var tree = new Ext.tree.TreePanel({
title:'SimpleTree',
width:300,
useArrows:true,
enableDD:true,
root:root,
renderTo:'renderTarget',
tbar:[
{ text: 'OpenAll', handler:function(){ tree.expandAll(); } },'-',
{ text: 'CloseAll', handler:function(){ tree.collapseAll(); } },'-',
{ text: 'AddNode-Recursive', handler:function(){
function recursiveNode( pNode ){
alert( 'this->' + this.toString() +
'\ndepth:' + pNode.getDepth() +
'\ntext:' + pNode.text +
'\nid:' + pNode.id );
pNode.eachChild( recursiveNode );
if( !pNode.isLeaf() ){
var setIdx = idx++;
pNode.appendChild( createNode( setIdx, 'add-' + setIdx, false, true ) );
}
};
root.eachChild( recursiveNode );
tree.expandAll();
}}
]
});
root.expand();
// node2はデフォルトクローズの状態
// node2.expand();
function createNode( id, text, isLeaf, isDrag ){
return new Ext.tree.TreeNode({
draggable:isDrag,
id:id,
text:text,
leaf:isLeaf
});
}
});
【Ext.tree.TreePanelの解説】
それではTreePanelを見ていきたいと思います。
var tree = new Ext.tree.TreePanel({
という箇所がツリーパネルそのものになります。ツリーを表現する以上、ノードとなるデータがひとつ以上必要になってきて、そのデータコンテンツはTreeNodeというクラスインスタンスを使うことになります。GridPanelやDataViewで使ったデータストアとは違ってくるようです。
ドキュメントには、以下のようにTreePanelがレンダリングされる前までに、コンフィグオプションのrootに有効なノードが入っていてくれとあります。コンフィグオプションのrootに直接突っ込んでもいいし、rootに対するセッターであるsetRootNode()メソッドでもよいとのことです。
A TreePanel must have a root node before it is rendered. This may either be specified using the root config option, or using the setRootNode method.
それではツリーパネルのコンフィグオプションを見てみます。
といっても、先述のとおり重要なコンフィグオプションはrootだけです。ここには次に述べるrootというTreeNodeのインスタンスを設定してあげてきます。
enableDD:true,は、ノードのドラッグアンドドロップが出来るかどうかです。TreeNodeインスタンスを作成するときにノード1つ1つに対してドラッグアンドドロップの設定が出来るのですが、TreePanelでfalseにすると全てのノードに対してドラッグアンドドロップが無効になります。
useArrows:true,は、表示系のコンフィグオプションで、これがfalseだとwindowsVista以前のエクスプローラのような[+]とか[-]のドリル表示になります。trueだとアロー型のドリル表示になります。
var tree = new Ext.tree.TreePanel({
title:'SimpleTree',
width:300,
useArrows:true,
enableDD:true,
root:root,
renderTo:'renderTarget',
続いてtbarというコンフィグオプションを設定していますが、この部分は最後に記述したいと思います。
【TreeNodeの解説】
次は、実際のデータであるTreeNodeの部分を見ていきます。
function createNode( id, text, isLeaf, isDrag ){
return new Ext.tree.TreeNode({
draggable:isDrag,
id:id,
text:text,
leaf:isLeaf
});
}
メソッドcreateNodeはワタシが勝手に作ったメソッドです。このメソッドはパラメータに従ってTreeNodeをnewして返すだけのものですので特に細かい解説はいらないかと思います。
ではTreeNodeのコンフィグオプションを見ていきます。まず、draggable:isDrag,はそのノードがドラッグアンドドロップできるかどうかを表していて、今回はパラメータに依存するつくりになっていて、trueでそのノードがドラッグアンドドロップできるようになり、falseで出来なくなります。
続いてidはそのツリー構造内のidを表すものです。明示的に設定しなくても勝手に振ってくれるようですが、ツリー構造内で一意にノードを特定したりすることがあるかと思いますので実装者が設定してあげたほうがいいと思います。ちなみに一意でなくとも動くには動くようです。
textは、そのままそのノードのテキスト表示部分です。このテキスト部分にはhrefというコンフィグオプションを加えることによってアンカーリンクを貼ることが出来ます。さらにhrefTargetでターゲットフレームの指定も出来ます。
最後のleafは、そのノードがリーフ(末端)であるかどうかです。leaf:trueですと、そのノード以下に子ノードを与えることが出来ません。leaf:falseだとブランチ(枝)を表していて子ノードを追加することが出来ます。
【Toolbarの解説】
最後にTreePanelの残りのコンフィグオプション、tbarについて解説したいと思います。tbarというコンフィグオプションはExt.Panelクラスを継承しているクラスであれば使えるコンフィグオプションです。~Panelという以下のパネル一覧であれば使えます。また、tbarはパネル上部にレンダリングされ、bbarはパネルの下部にレンダリングされます。
<パネル一覧>
EditorGridPanel Ext.grid.EditorGridPanel
FormPanel Ext.form.FormPanel
GridPanel Ext.grid.GridPanel
Panel Ext.Panel
TabPanel Ext.TabPanel
TreePanel Ext.tree.TreePanel
このtbarやbbarの実体はExt.Toolbarなのですが、このコンストラクタが以下のように追加したいボタンオブジェクトのコンフィグを配列としてパラメータに与えることが出来るので非常に分かりやすいです。
tbar:[
{ text: 'OpenAll', handler:function(){ tree.expandAll(); } },'-',
{ text: 'CloseAll', handler:function(){ tree.collapseAll(); } },'-',
{ text: 'AddNode-Recursive', handler:function(){
function recursiveNode( pNode ){
alert( 'depth:' + pNode.getDepth() + '\ntext:' + pNode.text + '\nid:' + pNode.id );
pNode.eachChild( recursiveNode );
if( !pNode.isLeaf() ){
var setIdx = idx++;
pNode.appendChild( createNode( setIdx, 'add-' + setIdx, false, true ) );
}
};
root.eachChild( recursiveNode );
tree.expandAll();
}}
]
もうちょっと詳しく説明すると、上記のようにオブジェクトを配列で渡した場合、その1つ1つはExt.Toolbar.Buttonオブジェクトを表しています。→
APIドキュメント
Ext.Toolbar.Buttonのコンフィグオプションには、上記のようにtextとhandlerをあたえれば、その表示と挙動を実装することが出来ます。コンフィグオプションにxtypeと言うものがあるのですが、これを与えないで置くと、デフォルトでtbbuttonでレンダリングされ、xtype:'tbsplit'とするとボタンの横に▼が表示されてメニュードロップの表示が出来るようになります。また、ボタンとボタンの間にセパレータ(|←こんなの)を表示させたい場合は、配列の要素として文字列の'-'を与えます。以下にToolbarコンポーネントのxtype一覧を記します
<Toolbar components:xtype一覧>
Toolbar components
---------------------------------------
toolbar Ext.Toolbar
tbbutton Ext.Toolbar.Button
tbfill Ext.Toolbar.Fill
tbitem Ext.Toolbar.Item
tbseparator Ext.Toolbar.Separator
tbspacer Ext.Toolbar.Spacer
tbsplit Ext.Toolbar.SplitButton
tbtext Ext.Toolbar.TextItem
実装の解説に戻りますが、handlerの内容は、上からノードヒエラルキーの全体オープンとクローズです。tree.expandAll() / tree.collapseAll()を使っています。
最後のAddNode-Recursiveと言うボタンは、ハンドラにちょこちょこ実装が入っていますが、内容はルートノードから全てのノードをアラートして、そのノードがブランチである場合、直下に1つブランチを追加していく実装となっています。
{ text: 'AddNode-Recursive', handler:function(){
function recursiveNode( pNode ){ /* ... */ };
root.eachChild( recursiveNode );
tree.expandAll();
}}
途中にrecursiveNodeというメソッドの定義がハンドラの中に入ってきていますのでちょっと分かりにくいですが、recursiveNodeを省いて見ると、このハンドラは
・ルートノードが持つ子ノード毎にrecursiveNodeと言うメソッドを呼んで
・最後にツリーを開くと言う処理だけです。
その"ノード毎に..."というところでeachChildメソッドが出てきましたので最後にTreeNodeに戻って解説します。
eachChild( Function fn, [Object scope], [Array args] ) : void
このメソッドはTreeNodeのメソッドなのですが、ノードが持つ子ノード分イテレートしてFunction fnを呼び出すというものです。第2引数のスコープは実際に与えるか、与えないとイテレート中のカレントのノードがFunction fnに与えられるみたいです。第3引数の"引数"も同様です。
ですので、function recursiveNode( pNode )の引数pNodeにはイテレート中のカレントノードが与えられてきます。そこから引数のpNodeで、さらにeachChildを呼び出して再帰します。再帰した後、ノードがブランチであれば、さらにブランチを追加すると言う仕組みになっています。
【余談】
1.マウスでノードをぐりぐりとドラッグアンドドロップしてみると、ちゃんとDepthが変わってくれます。ツリー構造とデータをそのまま維持しながらどこかにポストしたいとかがあれば、(jsonやXMLに変換する必要があるかもしれませんが)それも可能のようです。
2.今回はノードデータを自前で実装しましたが、Tree.TreeLoaderというデータのローダがあります。とても便利です。
というわけで、TreePanelの解説をしてみました。ワタシの理解を多分に含めておりますので、誤った情報である可能性がありますのでご注意ください。