初心者のためのExtJS入門

ExtJSを使うので、ついでにまとめていきます

ツリーパネル[classic] (1)

今回はclassicのツリーパネルを取り上げます。

ツリーパネルでは、エクスプローラディレクトリやファイルの位置関係を階層構造で表現するUIを提供できるビューです。

modernにも同じようなビューがありますが、classicとはクラス名が異なるため今回はclassicのみ対象とします(次はmodernを取り上げる予定)。

基本的な使い方

モデル、ストア

モデルにはExt.data.TreeModelクラス、ストアにはExt.data.TreeStoreクラスを使います。

Ext.define('Sample.model.Earning', {
    extend: 'Ext.data.TreeModel',

    fields: [
        {
            name: 'text',
            type: 'string'
        },
        {
            name: 'quarter1',
            type: 'int'
        },
        {
            name: 'quarter2',
            type: 'int'
        },
        {
            name: 'quarter3',
            type: 'int'
        },
        {
            name: 'quarter4',
            type: 'int'
        }
    ]
});

Ext.define('Sample.store.Earnings', {
    extend: 'Ext.data.TreeStore',

    requires: [
        'Ext.data.proxy.Memory'
    ],

    model: 'Sample.model.Earning',

    proxy: {
        type: 'memory'
    },

    root: {
        text: '',
        children: [
            {
                id: 1,
                text: '全体',
                quarter1: 20000,
                quarter2: 11100,
                quarter3: 5200,
                quarter4: 13600,
                expanded: true,
                iconCls: 'x-fa fa-building',
                children: [
                    {
                        id: 2,
                        text: 'グループ1',
                        quarter1: 1000,
                        quarter2: 800,
                        quarter3: 500,
                        quarter4: 1200,
                        iconCls: 'x-fa fa-users',
                        leaf: true
                    },
                    {
                        id: 3,
                        text: 'グループ2',
                        quarter1: 5000,
                        quarter2: 2300,
                        quarter3: 1200,
                        quarter4: 3000,
                        iconCls: 'x-fa fa-users',
                        children: [
                            {
                                id: 4,
                                text: 'グループ2-1',
                                quarter1: 3000,
                                quarter2: 1500,
                                quarter3: 700,
                                quarter4: 1100,
                                iconCls: 'x-fa fa-users',
                                leaf: true
                            },
                            {
                                id: 5,
                                text: 'グループ2-2',
                                quarter1: 2000,
                                quarter2: 800,
                                quarter3: 500,
                                quarter4: 1900,
                                iconCls: 'x-fa fa-users',
                                leaf: true
                            }
                        ]
                    },
                    {
                        id: 6,
                        text: 'グループ3',
                        quarter1: 14000,
                        quarter2: 8000,
                        quarter3: 3500,
                        quarter4: 9400,
                        expanded: true,
                        iconCls: 'x-fa fa-users',
                        children: [
                            {
                                id: 71,
                                text: 'グループ3-1',
                                quarter1: 1000,
                                quarter2: 2000,
                                quarter3: 500,
                                quarter4: 1100,
                                iconCls: 'x-fa fa-users',
                                leaf: true
                            },
                            {
                                id: 8,
                                text: 'グループ3-2',
                                quarter1: 5000,
                                quarter2: 2000,
                                quarter3: 2300,
                                quarter4: 500,
                                iconCls: 'x-fa fa-users',
                                leaf: true
                            },
                            {
                                id: 9,
                                text: 'グループ3-3',
                                quarter1: 8000,
                                quarter2: 4000,
                                quarter3: 700,
                                quarter4: 7800,
                                iconCls: 'x-fa fa-users',
                                leaf: true
                            }
                        ]
                    }
                ]
            }
        ]
    }
});

上記ではストアのデータをインメモリにしていますが、ツリーストア用のデータ構造にする必要がある点に注意してください。

まず、最上位の位置にルートノードが必要です。

さらにその直下に所属すべきノードをchildrenプロパティで並べていきます。末端のノードにはleaf: trueを指定しましょう。

ノードのtextプロパティは、ビューに表示されるテキストです。(これtextから変更できるのかな?捜したけど見つからなかったです)

idプロパティは内部的に使っているみたいで、モデルのフィールドに定義したらエラーとなりました。何かIDとして付けておきたい場合は、別のフィールド名で用意しておくようにしましょう。

他にも使えるプロパティがありますが、詳細は下記リンクからドキュメントを参照してください。Ext.data.NodeInterfaceのコンフィグがほとんどそのまま指定できます。

http://docs.sencha.com/extjs/6.5.1/classic/Ext.data.NodeInterface.html

http://docs.sencha.com/extjs/6.5.1/classic/Ext.data.TreeStore.html#cfg-root

ビュー

あとはExt.tree.Panelでビューを作成し、storeに先ほど作成したストアを指定します。

Ext.define('Sample.view.sample.Panel', {
    extend: 'Ext.tree.Panel',
    xtype: 'sample_panel',

    title: 'ツリーパネル サンプル',

    store: 'Earnings',

    rootVisible: false

});

ルートノードは必ず1つなので、表現したいデータの最上位ノートが複数の場合は、rootVisible: falseとすることでルートノードを表示しないように制御できます。

f:id:sham-memo:20170730111121p:plain

mtype, childTypeでノードごとにモデルを切り替えられる

ツリーストアの場合、ノードごとにモデルを指定できます(使うべき場面を思いつかなかった。。mtype, childTypeを知らなかったのでとりあえず書いときました)

http://docs.sencha.com/extjs/6.5.1/classic/Ext.tree.Panel.html

ロード

サーバにリクエストを投げる場合、まずは通常のストアのようにプロキシを設定します。

Ext.define('Sample.store.Earnings', {
    extend: 'Ext.data.TreeStore',

    model: 'Sample.model.Earning',

    proxy: {
        type: 'ajax',
        url: 'dummy/earnings.json'
    }
});

これでloadメソッドが呼ばれると、下記のようにnodeというリクエストパラメータ付きでリクエストされます。

f:id:sham-memo:20170730114847p:plain

これはTreeStoreのnodeParamとdefaultRootIdで変更できます。

Ext.define('Sample.store.Earnings', {
    extend: 'Ext.data.TreeStore',

    model: 'Sample.model.Earning',

    proxy: {
        type: 'ajax',
        url: 'dummy/earnings.json'
    },

    nodeParam: 'id',

    defaultRootId: 1
});

こうすると、id: 1でリクエストされます。(ちなみに0はダメみたいです。なら'root'のままでサーバ側で判定しても良いのかもね)

ルートノードより下位のノードでは、各ノードのidプロパティの値を使って展開時にリクエストされるようになっています。

一度展開したノードは、一旦閉じてから再度展開してもロードしないようです。

その場合もロードさせたい場合は、下記のように無理やりloadedをfalseにすればロードしてくれました。もっと良い方法を知っていれば誰か教えてください。

Ext.define('Sample.store.Earnings', {
    extend: 'Ext.data.TreeStore',

    model: 'Sample.model.Earning',

    proxy: {
        type: 'ajax',
        url: 'dummy/earnings.json'
    },

    nodeParam: 'id',

    defaultRootId: 1,

    listeners: {
        nodecollapse: function (node) {
            node.set('loaded', false);
        }
    }
});

ちなみに、サーバからは下記のようなレスポンスを返します。

{
  "text": "Root",
  "children": [
    {
      "text": "全体",
      "quarter1": 20000,
      "quarter2": 11100,
      "quarter3": 5200,
      "quarter4": 13600,
      "expanded": false,
      "iconCls": "x-fa fa-building"
    }
  ]
}

追加、削除

ノードの追加、削除はモデルに対して行います。

Ext.data.TreeModelはExt.data.NodeInterfaceの機能を使えるようになっているので、appendChild、removeメソッドを呼ぶことができます。

listeners: {
    selectionchange: function (view, records) {
        var record = records[0];

        if (record) {
            record.appendChild({
                id: 100,
                text: 'hogehoge',
                leaf: true
            });
        }
    }
}

listeners: {
    selectionchange: function (view, records) {
        var record = records[0];

        if (record) {
            record.remove(true);
        }
    }
}

selectionchangeのタイミングで追加、削除というかなり雑なコードになっていますが、モデルに対してこのように記述すると、対象ノードの子ノードとして追加したり削除したりできます。

ドラッグ&ドロップ

Ext.tree.plugin.TreeViewDragDropプラグインを使います。

 Ext.define('Sample.view.sample.Panel', {
    extend: 'Ext.tree.Panel',
    xtype: 'sample_panel',

    requires: [
        'Ext.tree.plugin.TreeViewDragDrop'
    ],

    title: 'ツリーパネル サンプル',

    viewConfig: {
        plugins: {
            treeviewdragdrop: true
        }
    },

    store: 'Earnings',

    rootVisible: false
});

そのうえで、ノードのallowDrag、allowDropでドラッグ、ドロップ可否を制御できます。

ドロップ時にbeforedrop、dropイベントが発生するので、処理はそのタイミングで実行します。