初心者のためのExtJS入門

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

チャート[classic] (2)

今回はclassicのチャートの続きです。

いくつかチャートの種類を試してみます。

Ext.chart.series.Line

折れ線グラフです。

insetPaddingやinnerPaddingでチャートのパディングを調整しています。何らかの方法で表示を調整しないと、テキストがチャート外にはみ出すことがあるので注意が必要です。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric',
        'Ext.chart.axis.Category',
        'Ext.chart.interactions.ItemHighlight',
        'Ext.chart.series.Line'
    ],

    store: 'WaterStorage',

    insetPadding: {
        top: 40,
        bottom: 40,
        left: 20,
        right: 40
    },

    innerPadding: {
        top: 20,
        left: 40,
        right: 40
    },

    axes: [
        {
            type: 'numeric',
            position: 'left',
            minimum: 0,
            title: {
                text: '貯水量'
            }
        },
        {
            type: 'category',
            position: 'bottom',
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'line',
            xField: 'ym',
            yField: 'amount',
            style: {
                stroke: '#ad5987',
                lineWidth: 2
            },
            marker: {
                type: 'circle',
                radius: 4,
                lineWidth: 2,
                fill: '#ad5987'
            },
            label: {
                field: 'amount',
                display: 'under'
            }
        }
    ]
});

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

Ext.chart.series.Bar

棒グラフです。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    store: 'WaterStorage',

    axes: [
        {
            type: 'numeric',
            position: 'left',
            minimum: 0,
            title: {
                text: '貯水量'
            }
        },
        {
            type: 'category',
            position: 'bottom',
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'bar',
            xField: 'ym',
            yField: 'amount',
            style: {
                minGapWidth: 20,
                stroke: '#50ada1',
                fill: '#50ada1'
            },
            highlight: {
                strokeStyle: '#4e7d9c',
                fillStyle: '#4e7d9c'
            },
            label: {
                field: 'amount',
                display: 'insideEnd'
            }
        }
    ]

});

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

↓のように水平方向にもできます。

Ext.chart.CartesianChartのflipXYコンフィグをtrueで指定し、軸(axes)の位置(position)を入れ替えています。

一応理屈は同じなので、Ext.chart.series.Lineなどでも可能です(需要はなさそうですが)。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric',
        'Ext.chart.axis.Category',
        'Ext.chart.interactions.ItemHighlight',
        'Ext.chart.series.Bar'
    ],

    store: 'WaterStorage',

    flipXY: true,

    axes: [
        {
            type: 'numeric',
            position: 'bottom',
            minimum: 0,
            title: {
                text: '貯水量'
            }
        },
        {
            type: 'category',
            position: 'left',
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'bar',
            xField: 'ym',
            yField: 'amount',
            style: {
                stroke: '#50ada1',
                fill: '#50ada1'
            },
            highlight: {
                strokeStyle: '#4e7d9c',
                fillStyle: '#4e7d9c'
            },
            label: {
                field: 'amount',
                display: 'insideEnd'
            }
        }
    ]
});

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

Ext.chart.series.Area

面グラフです。

highlightCfgコンフィグで、マウスがマーカーに近づいたときにマーカーのスタイルを変更できます。

あとtooltipコンフィグで、マウスがマーカーに近づいたときにツールチップを表示させています。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric',
        'Ext.chart.axis.Category',
        'Ext.chart.interactions.ItemHighlight',
        'Ext.chart.series.Area'
    ],

    store: 'WaterStorage',

    axes: [
        {
            type: 'numeric',
            position: 'left',
            grid: true,
            minimum: 0,
            title: {
                text: '貯水量'
            }
        },
        {
            type: 'category',
            position: 'bottom',
            label: {
                rotate: {
                    degrees: -90
                }
            },
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'area',
            xField: 'ym',
            yField: 'amount',
            style: {
                stroke: '#ad5987',
                fill: '#d9aac4',
                opacity: 0.6,
                lineWidth: 1
            },
            marker: {
                opacity: 0,
                scaling: 0,
                fx: {
                    duration: 200,
                    easing: 'easeOut'
                }
            },
            highlightCfg: {
                opacity: 1,
                scaling: 1.5
            },
            tooltip: {
                trackMouse: true,
                renderer: function (tooltip, record, item) {
                    tooltip.setHtml(record.get('ym') + ' : ' + record.get('amount'));
                }
            }
        }
    ]
});

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

とりあえず代表的なグラフを取り上げました。

まだいくつも種類があるので、もう少しチャート回が続きます。

チャート[classic] (1)

今回はclassicのチャートです。

まずはチャートを使うための基本を押さえておきます。

app.json

チャートの機能は別パッケージになっているので、app.jsonのrequiresにチャートのパッケージ名chartsを指定します。

"requires": [
    "charts"
]

実装イメージ

Ext.chartパッケージに~Chartというクラスがいくつか存在します。

チャートを表示するときは、軸(axesコンフィグ)とデータ系列(seriesコンフィグ)をそれらクラスに指定します。

この辺りは、数学で最初のころにグラフを覚えたときと同じです。

まずx, y軸の線を引いて、それからデータをプロットし、線グラフにしたり棒グラフにしたりといった感じでグラフを書いていくことでしょう。

ExtJSのチャートでも、axesで軸を定義し、seriesコンフィグで線グラフや棒グラフを指定します。

実装

チャートのデータはやはりストアで管理します。

なので、まずはモデルとストアを用意しました。

モデル、ストア

/**
 * 貯水量モデルクラス。
 *
 * @class Sample.model.WaterStorage
 * @extend Ext.data.Model
 */
Ext.define('Sample.model.WaterStorage', {
    extend: 'Ext.data.Model',

    fields: [
        {
            // 年月
            name: 'ym',
            type: 'string'
        },
        {
            // 量
            name: 'amount',
            type: 'int'
        }
    ]
});

/**
 * 貯水量ストアクラス。
 *
 * @class Sample.store.WaterStorage
 * @extend Ext.data.Store
 */
Ext.define('Sample.store.WaterStorage', {
    extend: 'Ext.data.Store',

    requires: [
        'Sample.model.WaterStorage'
    ],

    model: 'Sample.model.WaterStorage',

    proxy: 'memory',

    data: [
        { ym: '2016/01', amount: 100 },
        { ym: '2016/02', amount: 50 },
        { ym: '2016/03', amount: 30 },
        { ym: '2016/04', amount: 70 },
        { ym: '2016/05', amount: 50 },
        { ym: '2016/06', amount: 40 },
        { ym: '2016/07', amount: 30 },
        { ym: '2016/08', amount: 20 },
        { ym: '2016/09', amount: 40 },
        { ym: '2016/10', amount: 50 },
        { ym: '2016/11', amount: 60 },
        { ym: '2016/12', amount: 50 }
    ]
});

チャートクラスを作成

Ext.chart.CartesianChartを継承したチャートクラスを作成します。

最低限、store, axes, seriesコンフィグの指定が必要です。

/**
 * チャートクラス。
 *
 * @class Sample.views.main.chart.Panel
 * @extend Ext.chart.CartesianChart
 */
Ext.define('Sample.views.main.chart.Panel', {
    extend: 'Ext.chart.CartesianChart',
    xtype: 'chart_panel',

    requires: [
        'Ext.chart.axis.Numeric',
        'Ext.chart.axis.Category',
        'Ext.chart.interactions.ItemHighlight',
        'Ext.chart.series.Line'
    ],

    store: 'WaterStorage',

    axes: [
        {
            type: 'numeric',
            position: 'left',
            minimum: 0,
            title: {
                text: '貯水量'
            }
        },
        {
            type: 'category',
            position: 'bottom',
            title: {
                text: '年月'
            }
        }
    ],

    series: [
        {
            type: 'line',
            xField: 'ym',
            yField: 'amount',
            style: {
                stroke: '#ad5987',
                lineWidth: 2
            },
            marker: {
                type: 'circle',
                radius: 4,
                lineWidth: 2,
                fill: '#ad5987'
            },
            label: {
                field: 'amount',
                display: 'over'
            }
        }
    ]
});

axesコンフィグには、x軸とy軸の情報を指定します。y軸は貯水量を表現したいので、数値を扱えるtype: 'numeric'、x軸は各年月を表現したいので、どの型でもそのまま分類として扱えるtype: 'category'です。あと、どの位置かをpositionで設定します。

seriesコンフィグには、type: 'line'を設定していますが、これは線グラフのデータ系列です。x軸、y軸のデータとしてモデルのどのフィールドを使うかxField, yFieldで指定します。他のstyleやmarkerなどでスタイルなどの補足的な指定ができます。あと、チャート内に表示されている数値は、labelコンフィグが指定されているためです。

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

これでチャートを使うための基本は押さえました。

次回はExtJSで提供されている色々なチャートを使ってみます。

レイアウト[modern]

今回はmodernのレイアウトを取り上げます。

classicと説明が同じになっているところは、たぶん気のせいです。

Ext.layout.Default

これは特にレイアウトを指定していない場合に適用されているデフォルトのレイアウトです。

この場合、単純にDOMを順番に配置するだけです。アイテムコンポーネントのスタイル次第です。

/**
 * レイアウトDefaultのパネル。
 * 
 * @class Sample.view.main.layout.DefaultPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.DefaultPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_default_panel',

    layout: 'default',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

Ext.layout.Fit

アイテムコンポーネントを、layoutコンフィグを設定した親コンポーネントのサイズまで目一杯広げて設置するレイアウトです。

アイテムコンポーネントが複数ある場合は、表示できるものが1つだけ表示されます。

/**
 * レイアウトFitのパネル。
 *
 * @class Sample.view.main.layout.FitPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.FitPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_fit_panel',

    layout: 'fit',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

Ext.layout.Card

アイテムコンポーネントを、layoutコンフィグを設定した親コンポーネントのサイズまで目一杯広げて設置するレイアウトです。

アイテムコンポーネントが複数ある場合は、インデックス番号がより小さい表示可能な(非表示になっていない)アイテムコンポーネントが1つだけ表示されます。

Ext.layout.Fitに似ていますが、さらに表示するアイテムコンポーネントを切り替える機能を持ちます。

/**
 * レイアウトCardのパネル。
 *
 * @class Sample.view.main.layout.CardPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.CardPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_card_panel',

    layout: 'card',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

/**
 * メインパネル。
 *
 * @class Sample.view.main.Panel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.Panel', {
    extend: 'Ext.Panel',
    xtype: 'main_panel',

    cls: 'main-panel',

    requires: [
        'Sample.view.main.layout.CardPanel'
    ],

    layout: 'fit',

    tools: [
        {
            xtype: 'button',
            text: 'Next',
            ui: 'action',
            handler: function (btn) {
                var mainPanel = btn.up('main_panel'),
                    cardPanel = mainPanel.down('layout_card_panel'),
                    nextActive = cardPanel.items.indexOf(cardPanel.getActiveItem()) + 1;

                if (nextActive >= cardPanel.items.length) {
                    nextActive = 0;
                }

                cardPanel.setActiveItem(nextActive);
            }
        }
    ],

    items: {
        xtype: 'layout_card_panel'
    }
});

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

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

Ext.layout.Float

アイテムコンポーネントをfloatで並べます。

/**
 * レイアウトFloatのパネル。
 *
 * @class Sample.view.main.layout.FloatPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.FloatPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_float_panel',

    layout: 'float',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

Ext.layout.HBox

アイテムコンポーネントを横に並べるレイアウトです。

アイテムコンポーネントが右端に到達しても折り返しされません。

/**
 * レイアウトHBoxのパネル。
 *
 * @class Sample.view.main.layout.HboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.HboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_hbox_panel',

    scrollable: true,

    layout: 'hbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

レイアウトのalignコンフィグで、アイテムコンポーネントの縦方向に対する設定が可能です。alignは↓のように指定します。

layout: {
    type: 'hbox',
    align: 'middle'
}
middle end stretch
縦の位置を中央揃えにします。 縦の位置を下端に揃えます 高さを目一杯広げます
f:id:sham-memo:20170416214143p:plain f:id:sham-memo:20170416214247p:plain f:id:sham-memo:20170416213916p:plain

レイアウトのpackコンフィグで、アイテムコンポーネントの横方向に対する設定が可能です。

center end
横の位置を中央揃えにします 横の位置を右端に寄せます
f:id:sham-memo:20170416214755p:plain f:id:sham-memo:20170416214844p:plain

アイテムコンポーネントflexを指定することで、flexを指定したアイテムコンポーネントだけ目一杯幅を広げることができます。

/**
 * レイアウトHBoxのパネル。
 *
 * @class Sample.view.main.layout.HboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.HboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_hbox_panel',

    layout: 'hbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            flex: 1
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            flex: 2
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3',
            flex: 1
        }
    ]
});

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

flexを指定したアイテムコンポーネントがある場合、余った部分をflexの値の比率で分け合います。

Ext.layout.VBox

アイテムコンポーネントを縦に並べるレイアウトです。

向きが変わるだけで、Ext.layout.HBoxと同じようなことができます。

/**
 * レイアウトVBoxのパネル。
 *
 * @class Sample.view.main.layout.VboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.VboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_vbox_panel',

    layout: 'vbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

レイアウトのalignコンフィグで、アイテムコンポーネントの横方向に対する設定が可能です。alignは↓のように指定します。

middle end stretch
横の位置を中央揃えにします。 横の位置を右端に揃えます 幅を目一杯広げます
f:id:sham-memo:20170416215544p:plain f:id:sham-memo:20170416215641p:plain f:id:sham-memo:20170416215444p:plain

レイアウトのpackコンフィグで、アイテムコンポーネントの縦方向に対する設定が可能です。

center end
縦の位置を中央揃えにします 縦の位置を下端に寄せます
f:id:sham-memo:20170416215801p:plain f:id:sham-memo:20170416215906p:plain

アイテムコンポーネントflexを指定することで、flexを指定したアイテムコンポーネントだけ目一杯高さを広げることができます。

/**
 * レイアウトVBoxのパネル。
 *
 * @class Sample.view.main.layout.VboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.VboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_vbox_panel',

    layout: 'vbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            flex: 1
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            flex: 2
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

レイアウト[classic] (3)

classicのレイアウトは最後です。

Ext.layout.container.Column

アイテムコンポーネントを横に並べるレイアウトです。

端まで到達すると、floatのように自動的に折り返すところがHBoxと異なります。

/**
 * レイアウトColumnのパネル。
 *
 * @class Sample.view.main.layout.ColumnPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.ColumnPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_column_panel',

    layout: 'column',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

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

アイテムコンポーネントにcolumnWidthコンフィグを指定すると、幅を比率で指定することができます。

/**
 * レイアウトColumnのパネル。
 *
 * @class Sample.view.main.layout.ColumnPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.ColumnPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_column_panel',

    layout: 'column',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            columnWidth: 0.25
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            columnWidth: 0.75
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3',
            columnWidth: 0.5
        }
    ]
});

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

Ext.layout.container.Table

アイテムコンポーネントを表形式にレイアウトします。

イメージとしては、HTMLのtableタグです。実際に作成されるDOMはtableになります。

/**
 * レイアウトTableのパネル。
 *
 * @class Sample.view.main.layout.TablePanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.TablePanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_table_panel',

    layout: 'table',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

Ext.layout.container.Tableのcolumnsコンフィグを指定することで、表の列数を設定できます。

アイテムコンポーネントにrowspanやcolspanコンフィグを指定すると、列方向や行方向のセル結合も可能です。

/**
 * レイアウトTableのパネル。
 *
 * @class Sample.view.main.layout.TablePanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.TablePanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_table_panel',

    layout: {
        type: 'table',
        columns: 2
    },

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            colspan: 2
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

あと、アイテムコンポーネントにcellClsコンフィグを指定すると、td要素にクラス名を設定できます。

Ext.layout.container.Absolute

アイテムコンポーネントを絶対位置で配置できます。

座標はx, yコンフィグで指定します。

/**
 * レイアウトAbsoluteのパネル。
 *
 * @class Sample.view.main.layout.AbsolutePanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.AbsolutePanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_absolute_panel',

    layout: 'absolute',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            x: 100,
            y: 100
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            x: 30,
            y: 150
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]

});

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

レイアウト[classic] (2)

続きです。

Ext.layout.container.Card

アイテムコンポーネントを、layoutコンフィグを設定した親コンポーネントのサイズまで目一杯広げて設置するレイアウトです。

アイテムコンポーネントが複数ある場合は、インデックス番号がより小さい表示可能な(非表示になっていない)アイテムコンポーネントが1つだけ表示されます。

Ext.layout.container.Fitに似ていますが、さらに表示するアイテムコンポーネントを切り替える機能を持ちます。

/**
 * レイアウトCardのパネル。
 *
 * @class Sample.view.main.layout.CardPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.CardPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_card_panel',

    layout: 'card',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ],

    dockedItems: {
        dock: 'bottom',
        xtype: 'button',
        text: 'Next',
        handler: function (btn) {
            var panel = btn.up(),
                layout = panel.getLayout(),
                nextActive = panel.items.indexOf(layout.getActiveItem()) + 1;

            if (nextActive >= panel.items.length) {
                nextActive = 0;
            }

            layout.setActiveItem(nextActive);
        }
    }
});

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

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

Nextボタンを押すと、次のアイテムコンポーネントに切り替わります。

getActiveItem、setActiveItemメソッドを使って、現在表示されているアイテムコンポーネントを取得したり切り替えたりできます。

Ext.layout.container.Border

アイテムコンポーネントを、layoutコンフィグを設定した親コンポーネントの中央・東・西・南・北の位置に配置するレイアウトです。

どの位置に配置するかは、アイテムコンポーネントのregionコンフィグで指定します。

位置 regionに指定する文字列
中央 center
east
西 west
south
north
/**
 * レイアウトBorderのパネル。
 *
 * @class Sample.view.main.layout.BorderPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.BorderPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_border_panel',

    layout: 'border',

    items: [
        {
            region: 'center',
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            region: 'north',
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            region: 'east',
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

アイテムコンポーネントにweightコンフィグを指定すると、東西南北のどれを目一杯広げるかを制御できます。

weightの値が大きい方が優先されます。

/**
 * レイアウトBorderのパネル。
 *
 * @class Sample.view.main.layout.BorderPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.BorderPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_border_panel',

    layout: 'border',

    items: [
        {
            region: 'center',
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            region: 'north',
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            weight: 1
        },
        {
            region: 'east',
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3',
            weight: 2
        }
    ]
});

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

上記の場合、アイテムコンポーネント2よりアイテムコンポーネント3のほうがweightの値が大きいので、アイテムコンポーネント3が目一杯大きく表示されています。

Ext.layout.container.Anchor

このレイアウトでは、VBoxのようにアイテムコンポーネントは縦に並びます。

ただし、アイテムコンポーネントの大きさを親の大きさと相対的に指定することができるという特徴があります。

/**
 * レイアウトAnchorのパネル。
 *
 * @class Sample.view.main.layout.AnchorPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.AnchorPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_anchor_panel',

    scrollable: true,

    layout: 'anchor',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            anchor: '75% 20%'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            anchor: '-300 -200'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3',
            anchor: '-250 20%'
        }
    ]
});

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

アイテムコンポーネントのanchorコンフィグには、親との相対サイズ(幅 高さ)をパーセントまたはピクセルで指定します。

上記の場合、アイテムコンポーネント1はanchor: '75% 20%'としていますが、これは↓を意味します。

幅 = (親コンポーネントの幅) * 0.75
高さ = (親コンポーネントの高さ) * 0.2

また、アイテムコンポーネント2のanchor: '-300 -200'は↓を意味します。

幅 = (親コンポーネントの幅) - 300px
高さ = (親コンポーネントの高さ) - 200px

Ext.layout.container.Accordion

今では当たり前となったアコーディオンレイアウトです。

アコーディオンレイアウトの親コンポーネントに複数のアイテムコンポーネントを設置すると、1つだけ開くことができます。

注意点としては、アイテムコンポーネントはExt.Panelでなければなりません(もしくはExt.Panelを継承したクラス)。

なので、↓でもxtype: 'panel'としています。

/**
 * レイアウトAccordionのパネル。
 *
 * @class Sample.view.main.layout.AccordionPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.AccordionPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_accordion_panel',

    layout: 'accordion',

    items: [
        {
            xtype: 'panel',
            cls: 'item-panel1',
            title: 'アイテムコンポーネント1'
        },
        {
            xtype: 'panel',
            cls: 'item-panel2',
            title: 'アイテムコンポーネント2'
        },
        {
            xtype: 'panel',
            cls: 'item-panel3',
            title: 'アイテムコンポーネント3'
        }
    ]
});

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

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

Ext.layout.container.Center

アイテムコンポーネントを親コンポーネントの中央に配置するレイアウトです。

アイテムコンポーネントが複数ある場合は、最後の1つが表示されます。

/**
 * レイアウトCenterのパネル。
 *
 * @class Sample.view.main.layout.CenterPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.CenterPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_center_panel',

    layout: 'center',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            width: 200,
            height: 100
        }
    ]
});

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

レイアウト[classic] (1)

今回はclassicのレイアウトを取り上げます(何回かに分けます)。

レイアウトの種類によって、パネルやコンテナに設置するアイテムコンポーネントの配置が変わります。

レイアウトは、layoutコンフィグで設定します。

Ext.layout.container.Auto

これは特にレイアウトを指定していない場合に適用されているデフォルトのレイアウトです。

この場合、単純にDOMを順番に配置するだけです。アイテムコンポーネントのスタイル次第です。

アイテムコンポーネントがdisplay: blockなら、縦に並びます。float: leftでも付けていれば、横に並ぶことでしょう。

/**
 * レイアウトAutoのパネル。
 *
 * @class Sample.view.main.layout.AutoPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.AutoPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_auto_panel',

    scrollable: true,

    layout: 'auto',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

Ext.layout.container.Fit

アイテムコンポーネントを、layoutコンフィグを設定した親コンポーネントのサイズまで目一杯広げて設置するレイアウトです。

アイテムコンポーネントが複数ある場合は、表示できるものが1つだけ表示されます。

/**
 * レイアウトFitのパネル。
 *
 * @class Sample.view.main.layout.FitPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.FitPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_fit_panel',

    layout: 'fit',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            hidden: true
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

Ext.layout.container.HBox

アイテムコンポーネントを横に並べるレイアウトです。

アイテムコンポーネントが右端に到達しても折り返しされません。

/**
 * レイアウトHBoxのパネル。
 *
 * @class Sample.view.main.layout.HboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.HboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_hbox_panel',

    scrollable: true,

    layout: 'hbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

レイアウトのalignコンフィグで、アイテムコンポーネントの縦方向に対する設定が可能です。alignは↓のように指定します。

layout: {
    type: 'hbox',
    align: 'middle'
}
middle end stretch stretchmax
縦の位置を中央揃えにします。 縦の位置を下端に揃えます 高さを目一杯広げます 高さを一番高いアイテムコンポーネントに揃えます
f:id:sham-memo:20170401154648p:plain f:id:sham-memo:20170401155556p:plain f:id:sham-memo:20170401155313p:plain f:id:sham-memo:20170401155745p:plain

レイアウトのpackコンフィグで、アイテムコンポーネントの横方向に対する設定が可能です。

center end
横の位置を中央揃えにします 横の位置を右端に寄せます
f:id:sham-memo:20170401160336p:plain f:id:sham-memo:20170401160159p:plain

アイテムコンポーネントflexを指定することで、flexを指定したアイテムコンポーネントだけ目一杯幅を広げることができます。

/**
 * レイアウトHBoxのパネル。
 *
 * @class Sample.view.main.layout.HboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.HboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_hbox_panel',

    scrollable: true,

    layout: 'hbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            flex: 1
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            flex: 2
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

flexを指定したアイテムコンポーネントがある場合、余った部分をflexの値の比率で分け合います。

上記の例だと、親パネルの幅からアイテムコンポーネント3の幅を引いた部分が余った部分で、その余った部分を、アイテムコンポーネント1 : アイテムコンポーネント2 = 1 : 2の比率で分け合います。

Ext.layout.container.VBox

アイテムコンポーネントを縦に並べるレイアウトです。

向きが変わるだけで、Ext.layout.container.HBoxと同じようなことができます。

/**
 * レイアウトVBoxのパネル。
 *
 * @class Sample.view.main.layout.VboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.VboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_vbox_panel',

    layout: 'vbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1'
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2'
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

レイアウトのalignコンフィグで、アイテムコンポーネントの横方向に対する設定が可能です。alignは↓のように指定します。

middle end stretch stretchmax
横の位置を中央揃えにします。 横の位置を右端に揃えます 幅を目一杯広げます 幅を一番広いアイテムコンポーネントに揃えます
f:id:sham-memo:20170403225708p:plain f:id:sham-memo:20170403225822p:plain f:id:sham-memo:20170403225946p:plain f:id:sham-memo:20170403230208p:plain

レイアウトのpackコンフィグで、アイテムコンポーネントの縦方向に対する設定が可能です。

center end
縦の位置を中央揃えにします 縦の位置を下端に寄せます
f:id:sham-memo:20170403231149p:plain f:id:sham-memo:20170403231255p:plain

アイテムコンポーネントflexを指定することで、flexを指定したアイテムコンポーネントだけ目一杯高さを広げることができます。

/**
 * レイアウトVBoxのパネル。
 *
 * @class Sample.view.main.layout.VboxPanel
 * @extend Ext.Panel
 */
Ext.define('Sample.view.main.layout.VboxPanel', {
    extend: 'Ext.Panel',
    xtype: 'layout_vbox_panel',

    layout: 'vbox',

    items: [
        {
            xtype: 'component',
            cls: 'item-cmp1',
            html: 'アイテムコンポーネント1',
            flex: 1
        },
        {
            xtype: 'component',
            cls: 'item-cmp2',
            html: 'アイテムコンポーネント2',
            flex: 2
        },
        {
            xtype: 'component',
            cls: 'item-cmp3',
            html: 'アイテムコンポーネント3'
        }
    ]
});

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

Ext.XTemplateの使い方

今回は、Ext.XTemplateを取り上げます。

Ext.XTemplateはテンプレート機能を提供してくれます。同じようなパターンの文字列を何度も作成する場合に便利です。

多くのjavascriptフレームワークでも実装されており、HTMLタグ文字列を作成するのに使われることが多くです。

ExtJSでも同様に、多くのコンポーネントクラスの実装で使われており、使えるようになるとExtJSでの開発が非常に楽になります。

まずは使ってみる

これは挨拶の文字列を出力する簡単なサンプルです。

var tpl = new Ext.XTemplate('{name}さん、こんにちは');

var str = tpl.apply({
    name: '山田太郎'
});

console.log(str);

基本的な使い方は↓です。

1. Ext.XTemplateのコンストラクタにテンプレート用の文字列を渡す。
2. applyメソッドに埋め込みデータを渡す

applyメソッド以外にもデータを渡すためのメソッドは存在しますが、単純な文字列を返す場合はapplyメソッドを使います。

条件分岐

データの内容によって、テンプレート文字列で条件分岐することができます。

テンプレート文字列でを使うと、if内がtrueの場合、囲った部分が出力されます。

var tpl = new Ext.XTemplate(
    '{name}<tpl if="kana">({kana})</tpl>さん、こんにちは'
);

var str = tpl.apply({
    name: '山田太郎',
    kana: 'やまだたろう'
});

console.log(str);

上記のサンプルでは、埋め込みデータにkanaがあれば、ふりがなを括弧付きで出力します。

applyメソッドに渡すオブジェクトリテラルからkanaを消してみると、括弧部分も表示されなくなります。

大小関係を条件にする場合は、下記のようになります。(tplタグ内に書くので括弧が使えないのは良いとして、leやgeが存在しないのが謎です)

一般的なオペレータ テンプレート文字列でのオペレータ
> &gt;
>= &gt;=
< &lt;
<= &lt;=
== ==
var tpl = new Ext.XTemplate(
    '商品名: {name}\n',
    'サイズ: ',
    '<tpl if="weight &gt;= 1000">',
        '大',
    '<tpl elseif="weight &gt;= 500">',
        '中',
    '<tpl else>',
        '小',
    '</tpl>'
);

var str = tpl.apply({
    name: 'りんご',
    weight: 500
});

console.log(str);

これを実行するとサイズは「中」と出力されます。

ついでに使いましたが、elseifやelseで複数条件を指定することもできます。

繰り返し

を使って、配列なども繰り返し処理できます。

var tpl = new Ext.XTemplate(
    '<tpl for="items">',
        '商品名: {name}, 価格: {price}円\n',
    '</tpl>'
);

var str = tpl.apply({
    items: [
        {
            name: '帽子',
            price: 1800
        },
        {
            name: 'Tシャツ',
            price: 1000
        },
        {
            name: 'バッグ',
            price: 5000
        }
    ]
});

console.log(str);

forには、繰り返し処理するデータのプロパティ名を指定します。

すると、内では、繰り返し処理中の1件分が処理のスコープとなり、サンプルのように直接{name}や{price}が指定できます。

親のデータにアクセスしないといけない場合は、parentを通して取得します。

var tpl = new Ext.XTemplate(
    '<tpl for="items">',
        '商品名: {name}, 価格: {price}円, 税率: {parent.tax}\n',
    '</tpl>'
);

var str = tpl.apply({
    tax: 0.08,
    items: [
        {
            name: '帽子',
            price: 1800
        },
        {
            name: 'Tシャツ',
            price: 1000
        },
        {
            name: 'バッグ',
            price: 5000
        }
    ]
});

console.log(str);

ループのインデックス番号などもアクセスできます。

インデックス番号の場合は「xindex」です。他にもあるのでドキュメントを参照してみてください(http://docs.sencha.com/extjs/6.2.1/modern/Ext.XTemplate.html)。

var tpl = new Ext.XTemplate(
    '<tpl for="text">',
        '{% if (xindex % 2 === 0) continue; %}',
        '{.}\n',
        '{% if (xindex > 8) break; %}',
    '</tpl>'
);

var str = tpl.apply({
    text: [
        'item1',
        'item2',
        'item3',
        'item4',
        'item5',
        'item6',
        'item7',
        'item8',
        'item9',
        'item10'
    ]
});

console.log(str);

{% ... %}は、出力を伴わないような制御処理で使えます。出力まで伴う場合には、{[...]}を使うことができます(次のセクション)。

計算処理やメソッド定義

テンプレート内で同じような判定や計算があれば、メソッドにまとめることができます。

var tpl = new Ext.XTemplate(
    '<tpl for="items">',
        '商品名: {name}, 価格: {price}円',
        '<tpl if="!this.isTaxFree(values)">',
            ', 消費税: {[this.calcTax(values.price, parent.taxRate)]}円\n',
        '</tpl>',
    '</tpl>',
    {
        isTaxFree: function (values) {
            return values.taxFree;
        },
        calcTax: function (price, taxRate) {
            return Math.floor(price * taxRate);
        }
    }
);

var str = tpl.apply({
    taxRate: 0.08,
    items: [
        {
            name: '帽子',
            price: 1800,
            taxFree: false
        },
        {
            name: 'Tシャツ',
            price: 1000,
            taxFree: false
        },
        {
            name: 'バッグ',
            price: 5000,
            taxFree: true
        }
    ]
});

console.log(str);

Ext.XTemplateの最後にオブジェクトリテラルでメソッドを定義することができます。

出力させる場合は{[...]}に記述します。定義したメソッドはExt.XTemplateのメンバーメソッドになるので、thisキーワードから実行します。

埋め込みデータはvaluesに格納されているので、そこを経由してアクセスできます。

また、ifでもメソッドを使えるので、条件が複雑な場合はメソッドを定義すると便利です。

コンポーネントで使ってみる

最後にコンポーネントで使ってみましょう。

Ext.define('Sample.view.main.Panel', {
    extend: 'Ext.panel.Panel',
    xtype: 'main_panel',

    bodyPadding: 20,

    tpl: [
        '<header>',
            '<h1>{title}</h1>',
        '</header>',
        '<section>',
            '<p>{content}</p>',
        '</section>'
    ],

    data: {
        title: 'Ext.XTemplateの紹介',
        content: 'Ext.XTemplateを紹介しています。<br>便利な機能なのでぜひ使ってみてください。'
    }
});

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

上記の場合は、tplコンフィグがテンプレート文字列、dataコンフィグがそれに埋め込むデータとなります。

dataが設定されると、内部でExt.XTemplateが使われてDOMが生成・更新されるようになっているのです。